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
|
||||
|
||||
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.Column
|
||||
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.fillMaxSize
|
||||
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.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
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.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Tab
|
||||
@@ -48,20 +38,14 @@ import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
@@ -69,21 +53,12 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
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.TopBarWithBackButton
|
||||
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
||||
import com.vitorpamplona.amethyst.ui.screen.NostrUserListFeedViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.DisappearingScaffold
|
||||
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.TabRowHeight
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
@@ -120,10 +95,6 @@ fun ListsScreen(
|
||||
|
||||
val followSetsFlow by followSetsViewModel.feedContent.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(followSetsFlow) {
|
||||
followSetsViewModel.invalidateData()
|
||||
}
|
||||
|
||||
CustomListsScreen(
|
||||
followSetsFlow,
|
||||
refresh = {
|
||||
@@ -140,6 +111,12 @@ fun ListsScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
renameItem = { index, newValue ->
|
||||
followSetsViewModel.renameFollowSet(newName = newValue, setIndex = index)
|
||||
},
|
||||
deleteItem = {
|
||||
followSetsViewModel.deleteFollowSet(it)
|
||||
},
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
@@ -150,6 +127,8 @@ fun CustomListsScreen(
|
||||
followSetState: FollowSetState,
|
||||
refresh: () -> Unit,
|
||||
openItem: (identifier: String) -> Unit,
|
||||
renameItem: (Int, String) -> Unit,
|
||||
deleteItem: (Int) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
@@ -172,23 +151,24 @@ fun CustomListsScreen(
|
||||
Tab(
|
||||
selected = pagerState.currentPage == 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(
|
||||
selected = pagerState.currentPage == 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(
|
||||
selected = pagerState.currentPage == 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 = {
|
||||
// TODO: Add buttons for list creation and/or removal.
|
||||
// TODO: Show components based on current tab
|
||||
FollowSetFabsAndMenu()
|
||||
},
|
||||
) {
|
||||
Column(
|
||||
@@ -203,6 +183,8 @@ fun CustomListsScreen(
|
||||
followSetState = followSetState,
|
||||
onRefresh = refresh,
|
||||
onOpenItem = openItem,
|
||||
onRenameItem = renameItem,
|
||||
onDeleteItem = deleteItem,
|
||||
)
|
||||
|
||||
1 -> LabeledBookmarksFeedView()
|
||||
@@ -214,36 +196,36 @@ fun CustomListsScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FollowSetFeedView(
|
||||
modifier: Modifier = Modifier,
|
||||
followSetState: FollowSetState,
|
||||
onRefresh: () -> Unit = {},
|
||||
onOpenItem: (String) -> Unit = {},
|
||||
) {
|
||||
when (followSetState) {
|
||||
FollowSetState.Loading -> LoadingFeed()
|
||||
|
||||
is FollowSetState.Loaded -> {
|
||||
val followSetFeed = followSetState.feed
|
||||
FollowListLoaded(
|
||||
loadedFeedState = followSetFeed,
|
||||
onRefresh = onRefresh,
|
||||
onItemClick = onOpenItem,
|
||||
fun FollowSetFabsAndMenu(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
println("The private list addition...")
|
||||
},
|
||||
shape = CircleShape,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.lock_plus),
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
|
||||
is FollowSetState.Empty -> {
|
||||
FeedEmpty {
|
||||
onRefresh()
|
||||
}
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
println("The public list creation...")
|
||||
},
|
||||
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)
|
||||
@Composable
|
||||
private fun ListItemPreview() {
|
||||
@@ -463,6 +271,12 @@ private fun ListItemPreview() {
|
||||
onFollowSetClick = {
|
||||
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 =
|
||||
when {
|
||||
index % 2 == 0 -> ListVisibility.Private
|
||||
index in listOf(3, 7, 9) -> ListVisibility.Mixed
|
||||
else -> ListVisibility.Public
|
||||
},
|
||||
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