Separate components out into their logical groups(by file). Add new icons for public/private list creation, and add FloatingActionButtons for each. Add test code for iterating on changes quickly(TEMPORARY).
This commit is contained in:
+271
@@ -0,0 +1,271 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
|
||||||
|
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.People
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CustomListItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
followSet: FollowSet,
|
||||||
|
onFollowSetClick: () -> Unit,
|
||||||
|
onFollowSetRename: (String) -> Unit,
|
||||||
|
onFollowSetDelete: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.clickable(onClick = onFollowSetClick)
|
||||||
|
.border(
|
||||||
|
width = Dp.Hairline,
|
||||||
|
color = Color.Gray,
|
||||||
|
shape = RoundedCornerShape(percent = 20),
|
||||||
|
).padding(horizontal = 10.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(bottom = 12.dp)
|
||||||
|
.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(followSet.title, fontWeight = FontWeight.Bold)
|
||||||
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
FilterChip(
|
||||||
|
selected = true,
|
||||||
|
onClick = {},
|
||||||
|
label = {
|
||||||
|
Text(text = "${followSet.profileList.size}")
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.People,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = ButtonBorder,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
Text(
|
||||||
|
followSet.description ?: "",
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 2,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
followSet.visibility.let {
|
||||||
|
val text by derivedStateOf {
|
||||||
|
when (it) {
|
||||||
|
ListVisibility.Public -> "Public"
|
||||||
|
ListVisibility.Private -> "Private"
|
||||||
|
ListVisibility.Mixed -> "Mixed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(top = 15.dp),
|
||||||
|
verticalArrangement = Arrangement.Bottom,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter =
|
||||||
|
painterResource(
|
||||||
|
when (it) {
|
||||||
|
ListVisibility.Public -> R.drawable.ic_public
|
||||||
|
ListVisibility.Private -> R.drawable.lock
|
||||||
|
ListVisibility.Mixed -> R.drawable.format_list_bulleted_type
|
||||||
|
},
|
||||||
|
),
|
||||||
|
contentDescription = "Icon for $text List",
|
||||||
|
)
|
||||||
|
Text(text, color = Color.Gray, fontWeight = FontWeight.Light)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(start = 5.dp)
|
||||||
|
.padding(vertical = 7.dp),
|
||||||
|
verticalArrangement = Arrangement.Top,
|
||||||
|
horizontalAlignment = Alignment.End,
|
||||||
|
) {
|
||||||
|
ListOptionsButton(
|
||||||
|
followSetName = followSet.title,
|
||||||
|
onListRename = onFollowSetRename,
|
||||||
|
onListDelete = onFollowSetDelete,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ListOptionsButton(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
followSetName: String,
|
||||||
|
onListRename: (String) -> Unit,
|
||||||
|
onListDelete: () -> Unit,
|
||||||
|
) {
|
||||||
|
val isMenuOpen = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
ClickableBox(
|
||||||
|
onClick = { isMenuOpen.value = true },
|
||||||
|
) {
|
||||||
|
VerticalDotsIcon()
|
||||||
|
|
||||||
|
ListOptionsMenu(
|
||||||
|
listName = followSetName,
|
||||||
|
isExpanded = isMenuOpen.value,
|
||||||
|
onDismiss = { isMenuOpen.value = false },
|
||||||
|
onListRename = onListRename,
|
||||||
|
onDelete = onListDelete,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ListOptionsMenu(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
isExpanded: Boolean,
|
||||||
|
listName: String,
|
||||||
|
onListRename: (String) -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
val isRenameDialogOpen = remember { mutableStateOf(false) }
|
||||||
|
val renameString = remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = isExpanded,
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = "Delete")
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
println("The list named $listName has been selected for deletion.")
|
||||||
|
onDelete()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = "Rename list")
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
println("The list $listName should be renamed...")
|
||||||
|
isRenameDialogOpen.value = true
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRenameDialogOpen.value) {
|
||||||
|
RenameDialog(
|
||||||
|
renameString = renameString.value,
|
||||||
|
onStringRenameChange = {
|
||||||
|
renameString.value = it
|
||||||
|
},
|
||||||
|
onDismissDialog = { isRenameDialogOpen.value = false },
|
||||||
|
onListRename = {
|
||||||
|
onListRename(renameString.value)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenameDialog(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
renameString: String,
|
||||||
|
onStringRenameChange: (String) -> Unit,
|
||||||
|
onDismissDialog: () -> Unit,
|
||||||
|
onListRename: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissDialog,
|
||||||
|
title = {
|
||||||
|
Text(text = "Rename List")
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
TextField(
|
||||||
|
value = renameString,
|
||||||
|
onValueChange = onStringRenameChange,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onListRename(renameString)
|
||||||
|
onDismissDialog()
|
||||||
|
},
|
||||||
|
) { Text(text = "Rename") }
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
Button(onClick = onDismissDialog) { Text(text = "Cancel") }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
+50
-236
@@ -20,9 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -30,17 +27,10 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material.icons.filled.People
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
|
||||||
import androidx.compose.material3.FilterChip
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
@@ -48,20 +38,14 @@ import androidx.compose.material3.TabRow
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleEventObserver
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
@@ -69,21 +53,12 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableBox
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.INav
|
import com.vitorpamplona.amethyst.ui.navigation.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.TopBarWithBackButton
|
import com.vitorpamplona.amethyst.ui.navigation.TopBarWithBackButton
|
||||||
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserListFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.NostrUserListFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
@@ -120,10 +95,6 @@ fun ListsScreen(
|
|||||||
|
|
||||||
val followSetsFlow by followSetsViewModel.feedContent.collectAsStateWithLifecycle()
|
val followSetsFlow by followSetsViewModel.feedContent.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
LaunchedEffect(followSetsFlow) {
|
|
||||||
followSetsViewModel.invalidateData()
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomListsScreen(
|
CustomListsScreen(
|
||||||
followSetsFlow,
|
followSetsFlow,
|
||||||
refresh = {
|
refresh = {
|
||||||
@@ -140,6 +111,12 @@ fun ListsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
renameItem = { index, newValue ->
|
||||||
|
followSetsViewModel.renameFollowSet(newName = newValue, setIndex = index)
|
||||||
|
},
|
||||||
|
deleteItem = {
|
||||||
|
followSetsViewModel.deleteFollowSet(it)
|
||||||
|
},
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
@@ -150,6 +127,8 @@ fun CustomListsScreen(
|
|||||||
followSetState: FollowSetState,
|
followSetState: FollowSetState,
|
||||||
refresh: () -> Unit,
|
refresh: () -> Unit,
|
||||||
openItem: (identifier: String) -> Unit,
|
openItem: (identifier: String) -> Unit,
|
||||||
|
renameItem: (Int, String) -> Unit,
|
||||||
|
deleteItem: (Int) -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
@@ -172,23 +151,24 @@ fun CustomListsScreen(
|
|||||||
Tab(
|
Tab(
|
||||||
selected = pagerState.currentPage == 0,
|
selected = pagerState.currentPage == 0,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
|
||||||
text = { Text(text = "Follow Sets", overflow = TextOverflow.Ellipsis, maxLines = 1) },
|
text = { Text(text = "Follow Sets", overflow = TextOverflow.Visible) },
|
||||||
)
|
)
|
||||||
Tab(
|
Tab(
|
||||||
selected = pagerState.currentPage == 1,
|
selected = pagerState.currentPage == 1,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
|
||||||
text = { Text(text = "Labeled Bookmarks", overflow = TextOverflow.Ellipsis, maxLines = 1) },
|
text = { Text(text = "Labeled Bookmarks", overflow = TextOverflow.Visible) },
|
||||||
)
|
)
|
||||||
Tab(
|
Tab(
|
||||||
selected = pagerState.currentPage == 2,
|
selected = pagerState.currentPage == 2,
|
||||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
|
||||||
text = { Text(text = "General Bookmarks", overflow = TextOverflow.Ellipsis, maxLines = 1) },
|
text = { Text(text = "General Bookmarks", overflow = TextOverflow.Visible) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
floatingButton = {
|
floatingButton = {
|
||||||
// TODO: Add buttons for list creation and/or removal.
|
// TODO: Show components based on current tab
|
||||||
|
FollowSetFabsAndMenu()
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -203,6 +183,8 @@ fun CustomListsScreen(
|
|||||||
followSetState = followSetState,
|
followSetState = followSetState,
|
||||||
onRefresh = refresh,
|
onRefresh = refresh,
|
||||||
onOpenItem = openItem,
|
onOpenItem = openItem,
|
||||||
|
onRenameItem = renameItem,
|
||||||
|
onDeleteItem = deleteItem,
|
||||||
)
|
)
|
||||||
|
|
||||||
1 -> LabeledBookmarksFeedView()
|
1 -> LabeledBookmarksFeedView()
|
||||||
@@ -214,36 +196,36 @@ fun CustomListsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FollowSetFeedView(
|
fun FollowSetFabsAndMenu(modifier: Modifier = Modifier) {
|
||||||
modifier: Modifier = Modifier,
|
Row(
|
||||||
followSetState: FollowSetState,
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
onRefresh: () -> Unit = {},
|
) {
|
||||||
onOpenItem: (String) -> Unit = {},
|
FloatingActionButton(
|
||||||
) {
|
onClick = {
|
||||||
when (followSetState) {
|
println("The private list addition...")
|
||||||
FollowSetState.Loading -> LoadingFeed()
|
},
|
||||||
|
shape = CircleShape,
|
||||||
is FollowSetState.Loaded -> {
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
val followSetFeed = followSetState.feed
|
) {
|
||||||
FollowListLoaded(
|
Icon(
|
||||||
loadedFeedState = followSetFeed,
|
painter = painterResource(R.drawable.lock_plus),
|
||||||
onRefresh = onRefresh,
|
contentDescription = null,
|
||||||
onItemClick = onOpenItem,
|
tint = Color.White,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
FloatingActionButton(
|
||||||
is FollowSetState.Empty -> {
|
onClick = {
|
||||||
FeedEmpty {
|
println("The public list creation...")
|
||||||
onRefresh()
|
},
|
||||||
}
|
shape = CircleShape,
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.earth_plus),
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is FollowSetState.FeedError ->
|
|
||||||
FeedError(
|
|
||||||
followSetState.errorMessage,
|
|
||||||
) {
|
|
||||||
onRefresh()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,180 +253,6 @@ fun GeneralBookmarksFeedView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun FollowListLoaded(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
loadedFeedState: List<FollowSet>,
|
|
||||||
onRefresh: () -> Unit = {},
|
|
||||||
onItemClick: (String) -> Unit = {},
|
|
||||||
) {
|
|
||||||
Log.d("FollowSetComposable", "FollowListLoaded: Follow Set size: ${loadedFeedState.size}")
|
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
|
||||||
RefresheableBox(
|
|
||||||
onRefresh = onRefresh,
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
contentPadding = FeedPadding,
|
|
||||||
) {
|
|
||||||
itemsIndexed(loadedFeedState, key = { _, item -> item.identifierTag }) { index, set ->
|
|
||||||
CustomListItem(
|
|
||||||
followSet = set,
|
|
||||||
onFollowSetClick = {
|
|
||||||
onItemClick(set.identifierTag)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun CustomListItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
followSet: FollowSet,
|
|
||||||
onFollowSetClick: () -> Unit,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier =
|
|
||||||
modifier
|
|
||||||
.clickable(onClick = onFollowSetClick)
|
|
||||||
.border(
|
|
||||||
width = Dp.Hairline,
|
|
||||||
color = Color.Gray,
|
|
||||||
shape = RoundedCornerShape(percent = 20),
|
|
||||||
).padding(horizontal = 10.dp),
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier =
|
|
||||||
modifier
|
|
||||||
.padding(bottom = 12.dp)
|
|
||||||
.weight(1f),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = modifier.weight(1f),
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
Text(followSet.title, fontWeight = FontWeight.Bold)
|
|
||||||
Spacer(modifier = StdHorzSpacer)
|
|
||||||
FilterChip(
|
|
||||||
selected = true,
|
|
||||||
onClick = {},
|
|
||||||
label = {
|
|
||||||
Text(text = "${followSet.profileList.size}")
|
|
||||||
},
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.People,
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
shape = ButtonBorder,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = StdVertSpacer)
|
|
||||||
Text(
|
|
||||||
followSet.description ?: "",
|
|
||||||
fontWeight = FontWeight.Light,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
maxLines = 2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
followSet.visibility.let {
|
|
||||||
val text by derivedStateOf {
|
|
||||||
when (it) {
|
|
||||||
ListVisibility.Public -> "Public"
|
|
||||||
ListVisibility.Private -> "Private"
|
|
||||||
ListVisibility.Mixed -> "Mixed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(
|
|
||||||
modifier = modifier.padding(top = 15.dp),
|
|
||||||
verticalArrangement = Arrangement.Bottom,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter =
|
|
||||||
painterResource(
|
|
||||||
when (it) {
|
|
||||||
ListVisibility.Public -> R.drawable.ic_public
|
|
||||||
ListVisibility.Private -> R.drawable.lock
|
|
||||||
ListVisibility.Mixed -> R.drawable.format_list_bulleted_type
|
|
||||||
},
|
|
||||||
),
|
|
||||||
contentDescription = "Icon for $text List",
|
|
||||||
)
|
|
||||||
Text(text, color = Color.Gray, fontWeight = FontWeight.Light)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier.padding(vertical = 7.dp),
|
|
||||||
verticalArrangement = Arrangement.Top,
|
|
||||||
horizontalAlignment = Alignment.End,
|
|
||||||
) {
|
|
||||||
ListOptionsButton(followSetName = followSet.title)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ListOptionsButton(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
followSetName: String,
|
|
||||||
) {
|
|
||||||
val isMenuOpen = remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
ClickableBox(
|
|
||||||
onClick = { isMenuOpen.value = true },
|
|
||||||
) {
|
|
||||||
VerticalDotsIcon()
|
|
||||||
|
|
||||||
ListOptionsMenu(
|
|
||||||
listName = followSetName,
|
|
||||||
isExpanded = isMenuOpen.value,
|
|
||||||
onDismiss = { isMenuOpen.value = false },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ListOptionsMenu(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
isExpanded: Boolean,
|
|
||||||
listName: String,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
) {
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = isExpanded,
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = {
|
|
||||||
Text(text = "Delete")
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
println("The list named $listName has been selected for deletion.")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
|
||||||
text = {
|
|
||||||
Text(text = "Rename list")
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
println("The list $listName should be renamed...")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(showSystemUi = true)
|
@Preview(showSystemUi = true)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ListItemPreview() {
|
private fun ListItemPreview() {
|
||||||
@@ -463,6 +271,12 @@ private fun ListItemPreview() {
|
|||||||
onFollowSetClick = {
|
onFollowSetClick = {
|
||||||
println("follow set: ${sampleFollowSet.identifierTag}")
|
println("follow set: ${sampleFollowSet.identifierTag}")
|
||||||
},
|
},
|
||||||
|
onFollowSetRename = {
|
||||||
|
println("Follow set new name: $it")
|
||||||
|
},
|
||||||
|
onFollowSetDelete = {
|
||||||
|
println(" The follow set ${sampleFollowSet.title} has been deleted.")
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+139
@@ -0,0 +1,139 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FollowSetFeedView(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
followSetState: FollowSetState,
|
||||||
|
onRefresh: () -> Unit = {},
|
||||||
|
onOpenItem: (String) -> Unit = {},
|
||||||
|
onRenameItem: (Int, String) -> Unit,
|
||||||
|
onDeleteItem: (Int) -> Unit,
|
||||||
|
) {
|
||||||
|
when (followSetState) {
|
||||||
|
FollowSetState.Loading -> LoadingFeed()
|
||||||
|
|
||||||
|
is FollowSetState.Loaded -> {
|
||||||
|
val followSetFeed = followSetState.feed
|
||||||
|
FollowListLoaded(
|
||||||
|
loadedFeedState = followSetFeed,
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
onItemClick = onOpenItem,
|
||||||
|
onItemRename = onRenameItem,
|
||||||
|
onItemDelete = onDeleteItem,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is FollowSetState.Empty -> {
|
||||||
|
FollowListFeedEmpty(
|
||||||
|
message =
|
||||||
|
"It seems you do not have any follow lists yet.\n " +
|
||||||
|
"Tap below to refresh, or tap the add buttons to create a new one.",
|
||||||
|
) {
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is FollowSetState.FeedError ->
|
||||||
|
FeedError(
|
||||||
|
followSetState.errorMessage,
|
||||||
|
) {
|
||||||
|
onRefresh()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FollowListLoaded(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
loadedFeedState: List<FollowSet>,
|
||||||
|
onRefresh: () -> Unit = {},
|
||||||
|
onItemClick: (String) -> Unit = {},
|
||||||
|
onItemRename: (index: Int, newName: String) -> Unit,
|
||||||
|
onItemDelete: (itemIndex: Int) -> Unit,
|
||||||
|
) {
|
||||||
|
Log.d("FollowSetComposable", "FollowListLoaded: Follow Set size: ${loadedFeedState.size}")
|
||||||
|
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
RefresheableBox(
|
||||||
|
onRefresh = onRefresh,
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
contentPadding = FeedPadding,
|
||||||
|
) {
|
||||||
|
itemsIndexed(loadedFeedState, key = { _, item -> item.identifierTag }) { index, set ->
|
||||||
|
CustomListItem(
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
|
followSet = set,
|
||||||
|
onFollowSetClick = {
|
||||||
|
onItemClick(set.identifierTag)
|
||||||
|
},
|
||||||
|
onFollowSetRename = {
|
||||||
|
onItemRename(index, it)
|
||||||
|
},
|
||||||
|
onFollowSetDelete = {
|
||||||
|
onItemDelete(index)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FollowListFeedEmpty(
|
||||||
|
message: String = stringRes(R.string.feed_is_empty),
|
||||||
|
onRefresh: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
Modifier.fillMaxSize(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Text(message)
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
OutlinedButton(onClick = onRefresh) { Text(text = stringRes(R.string.refresh)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
-1
@@ -181,7 +181,6 @@ fun generateFollowLists(): List<FollowSet> =
|
|||||||
visibility =
|
visibility =
|
||||||
when {
|
when {
|
||||||
index % 2 == 0 -> ListVisibility.Private
|
index % 2 == 0 -> ListVisibility.Private
|
||||||
index in listOf(3, 7, 9) -> ListVisibility.Mixed
|
|
||||||
else -> ListVisibility.Public
|
else -> ListVisibility.Public
|
||||||
},
|
},
|
||||||
profileList = emptySet(),
|
profileList = emptySet(),
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<!-- drawable/earth_plus.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M17 14H19V17H22V19H19V22H17V19H14V17H17V14M20 12C20 8.64 17.93 5.77 15 4.59V5C15 6.1 14.1 7 13 7H11V9C11 9.55 10.55 10 10 10H8V12H14C14.5 12 14.9 12.35 15 12.81C13.2 13.85 12 15.79 12 18C12 19.5 12.54 20.85 13.44 21.9L12 22C6.5 22 2 17.5 2 12C2 6.5 6.5 2 12 2C17.5 2 22 6.5 22 12L21.9 13.44C21.34 12.96 20.7 12.59 20 12.34L20 12M11 19.93V18C9.9 18 9 17.1 9 16V15L4.21 10.21C4.08 10.78 4 11.38 4 12C4 16.08 7.06 19.44 11 19.93Z" /></vector>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<!-- drawable/lock_plus.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M19 13C19.34 13 19.67 13.04 20 13.09V10C20 8.9 19.11 8 18 8H17V6C17 3.24 14.76 1 12 1S7 3.24 7 6V8H6C4.9 8 4 8.89 4 10V20C4 21.11 4.89 22 6 22H13.81C13.3 21.12 13 20.1 13 19C13 15.69 15.69 13 19 13M9 6C9 4.34 10.34 3 12 3S15 4.34 15 6V8H9V6M12 17C10.9 17 10 16.11 10 15S10.9 13 12 13C13.1 13 14 13.89 14 15C14 16.11 13.11 17 12 17M23 18V20H20V23H18V20H15V18H18V15H20V18H23Z" /></vector>
|
||||||
Reference in New Issue
Block a user