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:
KotlinGeekDev
2025-04-30 17:11:28 +01:00
parent 0693e4412f
commit 5a6cfad848
6 changed files with 462 additions and 237 deletions
@@ -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") }
},
)
}
@@ -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.")
},
)
}
}
@@ -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)) }
}
}
@@ -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>