Refactor UI to adapt to correct way of managing follow sets.

This commit is contained in:
KotlinGeekDev
2025-09-30 15:38:16 +01:00
parent dfb683e00d
commit f6b3fdcab4
5 changed files with 258 additions and 190 deletions

View File

@@ -30,9 +30,11 @@ import androidx.compose.foundation.layout.padding
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.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
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,8 +50,6 @@ 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.platform.LocalContext
import androidx.compose.ui.res.painterResource
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
@@ -119,12 +119,11 @@ fun ListsAndSetsScreen(
refresh = { refresh = {
followSetsViewModel.invalidateData() followSetsViewModel.invalidateData()
}, },
addItem = { title: String, description: String?, listType: SetVisibility -> addItem = { title: String, description: String? ->
val isSetPrivate = listType == SetVisibility.Private
followSetsViewModel.addFollowSet( followSetsViewModel.addFollowSet(
setName = title, setName = title,
setDescription = description, setDescription = description,
isListPrivate = isSetPrivate,
account = accountViewModel.account, account = accountViewModel.account,
) )
}, },
@@ -153,14 +152,14 @@ fun ListsAndSetsScreen(
fun CustomListsScreen( fun CustomListsScreen(
followSetFeedState: FollowSetFeedState, followSetFeedState: FollowSetFeedState,
refresh: () -> Unit, refresh: () -> Unit,
addItem: (title: String, description: String?, listType: SetVisibility) -> Unit, addItem: (title: String, description: String?) -> Unit,
openItem: (identifier: String) -> Unit, openItem: (identifier: String) -> Unit,
renameItem: (followSet: FollowSet, newName: String) -> Unit, renameItem: (followSet: FollowSet, newName: String) -> Unit,
deleteItem: (followSet: FollowSet) -> Unit, deleteItem: (followSet: FollowSet) -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
) { ) {
val pagerState = rememberPagerState { 3 } val pagerState = rememberPagerState { 2 }
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
DisappearingScaffold( DisappearingScaffold(
@@ -185,22 +184,14 @@ fun CustomListsScreen(
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = { Text(text = stringRes(R.string.labeled_bookmarks), overflow = TextOverflow.Visible) }, text = { Text(text = stringRes(R.string.labeled_bookmarks), overflow = TextOverflow.Visible) },
) )
Tab(
selected = pagerState.currentPage == 2,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
text = { Text(text = stringRes(R.string.general_bookmarks), overflow = TextOverflow.Visible) },
)
} }
} }
}, },
floatingButton = { floatingButton = {
// TODO: Show components based on current tab // TODO: Show components based on current tab
FollowSetFabsAndMenu( FollowSetFabsAndMenu(
onAddPrivateSet = { name: String, description: String? -> onAddSet = { name: String, description: String? ->
addItem(name, description, SetVisibility.Private) addItem(name, description)
},
onAddPublicSet = { name: String, description: String? ->
addItem(name, description, SetVisibility.Public)
}, },
) )
}, },
@@ -226,7 +217,7 @@ fun CustomListsScreen(
) )
1 -> LabeledBookmarksFeedView() 1 -> LabeledBookmarksFeedView()
2 -> GeneralBookmarksFeedView() // 2 -> GeneralBookmarksFeedView()
} }
} }
} }
@@ -236,57 +227,34 @@ fun CustomListsScreen(
@Composable @Composable
private fun FollowSetFabsAndMenu( private fun FollowSetFabsAndMenu(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onAddPrivateSet: (name: String, description: String?) -> Unit, onAddSet: (name: String, description: String?) -> Unit,
onAddPublicSet: (name: String, description: String?) -> Unit,
) { ) {
val isSetAdditionDialogOpen = remember { mutableStateOf(false) } val isSetAdditionDialogOpen = remember { mutableStateOf(false) }
val isPrivateOptionTapped = remember { mutableStateOf(false) }
Row( ExtendedFloatingActionButton(
horizontalArrangement = Arrangement.spacedBy(10.dp), text = {
) { Text(text = "New")
FloatingActionButton( },
onClick = { icon = {
isPrivateOptionTapped.value = true
isSetAdditionDialogOpen.value = true
},
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon( Icon(
painter = painterResource(R.drawable.lock_plus), imageVector = Icons.AutoMirrored.Filled.PlaylistAdd,
contentDescription = null, contentDescription = null,
tint = Color.White,
) )
} },
FloatingActionButton( onClick = {
onClick = { isSetAdditionDialogOpen.value = true
isSetAdditionDialogOpen.value = true },
}, shape = CircleShape,
shape = CircleShape, containerColor = MaterialTheme.colorScheme.primary,
containerColor = MaterialTheme.colorScheme.primary, )
) {
Icon(
painter = painterResource(R.drawable.earth_plus),
contentDescription = null,
tint = Color.White,
)
}
}
if (isSetAdditionDialogOpen.value) { if (isSetAdditionDialogOpen.value) {
NewSetCreationDialog( NewSetCreationDialog(
onDismiss = { onDismiss = {
isSetAdditionDialogOpen.value = false isSetAdditionDialogOpen.value = false
isPrivateOptionTapped.value = false
}, },
shouldBePrivate = isPrivateOptionTapped.value,
onCreateList = { name, description -> onCreateList = { name, description ->
if (isPrivateOptionTapped.value) { onAddSet(name, description)
onAddPrivateSet(name, description)
} else {
onAddPublicSet(name, description)
}
}, },
) )
} }
@@ -296,27 +264,10 @@ private fun FollowSetFabsAndMenu(
fun NewSetCreationDialog( fun NewSetCreationDialog(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onDismiss: () -> Unit, onDismiss: () -> Unit,
shouldBePrivate: Boolean,
onCreateList: (name: String, description: String?) -> Unit, onCreateList: (name: String, description: String?) -> Unit,
) { ) {
val newListName = remember { mutableStateOf("") } val newListName = remember { mutableStateOf("") }
val newListDescription = remember { mutableStateOf<String?>(null) } val newListDescription = remember { mutableStateOf<String?>(null) }
val context = LocalContext.current
val listTypeText =
stringRes(
context,
when (shouldBePrivate) {
true -> R.string.follow_set_type_private
false -> R.string.follow_set_type_public
},
)
val listTypeIcon =
when (shouldBePrivate) {
true -> R.drawable.lock
false -> R.drawable.ic_public
}
AlertDialog( AlertDialog(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
@@ -325,12 +276,8 @@ fun NewSetCreationDialog(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
Icon(
painter = painterResource(listTypeIcon),
contentDescription = null,
)
Text( Text(
text = stringRes(R.string.follow_set_creation_dialog_title, listTypeText), text = stringRes(R.string.follow_set_creation_dialog_title),
) )
} }
}, },
@@ -347,7 +294,7 @@ fun NewSetCreationDialog(
}, },
) )
Spacer(modifier = DoubleVertSpacer) Spacer(modifier = DoubleVertSpacer)
// For the list description // For the set description
TextField( TextField(
value = value =
( (

View File

@@ -37,13 +37,10 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
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.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
@@ -56,7 +53,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
@@ -64,7 +60,6 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.Size5dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import kotlin.let
@Composable @Composable
fun CustomSetItem( fun CustomSetItem(
@@ -96,20 +91,75 @@ fun CustomSetItem(
) { ) {
Text(followSet.title, fontWeight = FontWeight.Bold) Text(followSet.title, fontWeight = FontWeight.Bold)
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
FilterChip( if (followSet.publicProfiles.isEmpty() && followSet.privateProfiles.isEmpty()) {
selected = true, FilterChip(
onClick = {}, selected = true,
label = { onClick = {},
Text(text = "${followSet.profiles.size}") label = {
}, Text(text = "No members")
leadingIcon = { },
Icon( leadingIcon = {
imageVector = Icons.Default.People, Icon(
contentDescription = null, imageVector = Icons.Default.People,
contentDescription = null,
)
},
shape = ButtonBorder,
)
}
if (followSet.publicProfiles.isNotEmpty()) {
val publicMemberSize = followSet.publicProfiles.size
val membersLabel =
stringRes(
context,
if (publicMemberSize == 1) {
R.string.follow_set_single_member_label
} else {
R.string.follow_set_multiple_member_label
},
) )
}, FilterChip(
shape = ButtonBorder, selected = true,
) onClick = {},
label = {
Text(text = "$publicMemberSize $membersLabel")
},
leadingIcon = {
Icon(
painterResource(R.drawable.ic_public),
contentDescription = null,
)
},
shape = ButtonBorder,
)
Spacer(modifier = StdHorzSpacer)
}
if (followSet.privateProfiles.isNotEmpty()) {
val privateMemberSize = followSet.privateProfiles.size
val membersLabel =
stringRes(
context,
if (privateMemberSize == 1) {
R.string.follow_set_single_member_label
} else {
R.string.follow_set_multiple_member_label
},
)
FilterChip(
selected = true,
onClick = {},
label = {
Text(text = "$privateMemberSize $membersLabel")
},
leadingIcon = {
Icon(
painterResource(R.drawable.lock),
contentDescription = null,
)
},
shape = ButtonBorder,
)
}
} }
Spacer(modifier = StdVertSpacer) Spacer(modifier = StdVertSpacer)
Text( Text(
@@ -119,34 +169,6 @@ fun CustomSetItem(
maxLines = 2, maxLines = 2,
) )
} }
followSet.visibility.let {
val text by derivedStateOf {
when (it) {
SetVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
SetVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
SetVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
}
}
Column(
modifier = Modifier.padding(top = 15.dp),
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter =
painterResource(
when (it) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = stringRes(R.string.follow_set_type_description, text),
)
Text(text, color = Color.Gray, fontWeight = FontWeight.Light)
}
}
} }
Column( Column(

View File

@@ -28,6 +28,8 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@@ -68,7 +70,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet import com.vitorpamplona.amethyst.model.nip51Lists.followSets.FollowSet
import com.vitorpamplona.amethyst.model.nip51Lists.followSets.SetVisibility
import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.ClickableBox
import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserCompose
@@ -81,6 +82,7 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.HalfPadding import com.vitorpamplona.amethyst.ui.theme.HalfPadding
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.VertPadding
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -144,7 +146,8 @@ fun FollowSetScreen(
when { when {
selectedSetState.value != null -> { selectedSetState.value != null -> {
val selectedSet = selectedSetState.value val selectedSet = selectedSetState.value
val users = selectedSet!!.profiles.mapToUsers(accountViewModel).filterNotNull() val publicMembers = selectedSet!!.publicProfiles.mapToUsers(accountViewModel).filterNotNull()
val privateMembers = selectedSet.privateProfiles.mapToUsers(accountViewModel).filterNotNull()
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -171,6 +174,7 @@ fun FollowSetScreen(
selectedSet, selectedSet,
accountViewModel.account, accountViewModel.account,
) )
navigator.popBack()
}, },
) )
}, },
@@ -190,7 +194,8 @@ fun FollowSetScreen(
bottom = padding.calculateBottomPadding(), bottom = padding.calculateBottomPadding(),
).consumeWindowInsets(padding) ).consumeWindowInsets(padding)
.imePadding(), .imePadding(),
followSetList = users, publicMemberList = publicMembers,
privateMemberList = privateMembers,
onDeleteUser = { onDeleteUser = {
followSetViewModel.removeUserFromSet( followSetViewModel.removeUserFromSet(
it, it,
@@ -233,14 +238,7 @@ fun TitleAndDescription(
) )
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
Icon( Icon(
painter = painter = painterResource(R.drawable.format_list_bulleted_type),
painterResource(
when (followSet.setVisibility) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = null, contentDescription = null,
) )
} }
@@ -260,7 +258,8 @@ fun TitleAndDescription(
@Composable @Composable
private fun FollowSetListView( private fun FollowSetListView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
followSetList: List<User>, publicMemberList: List<User>,
privateMemberList: List<User>,
onDeleteUser: (String) -> Unit, onDeleteUser: (String) -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
nav: INav, nav: INav,
@@ -272,14 +271,61 @@ private fun FollowSetListView(
contentPadding = FeedPadding, contentPadding = FeedPadding,
state = listState, state = listState,
) { ) {
itemsIndexed(followSetList, key = { _, item -> item.pubkeyHex }) { _, item -> if (publicMemberList.isNotEmpty()) {
FollowSetListItem( stickyHeader {
modifier = Modifier.animateItem(), Column(
user = item, modifier = VertPadding,
accountViewModel = accountViewModel, ) {
nav = nav, Text(
onDeleteUser = onDeleteUser, text = "Public Profiles",
) fontSize = 18.sp,
fontWeight = FontWeight.Bold,
)
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 2.dp,
color = MaterialTheme.colorScheme.onBackground,
)
}
}
itemsIndexed(publicMemberList, key = { _, item -> item.pubkeyHex }) { _, item ->
FollowSetListItem(
modifier = Modifier.animateItem(),
user = item,
accountViewModel = accountViewModel,
nav = nav,
onDeleteUser = onDeleteUser,
)
}
item {
Spacer(modifier = Modifier.height(30.dp))
}
}
if (privateMemberList.isNotEmpty()) {
stickyHeader {
Text(
text = "Private Profiles",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
)
HorizontalDivider(
modifier = Modifier.fillMaxWidth(),
thickness = 2.dp,
color = MaterialTheme.colorScheme.onBackground,
)
}
itemsIndexed(privateMemberList, key = { _, item -> item.pubkeyHex }) { _, item ->
FollowSetListItem(
modifier = Modifier.animateItem(),
user = item,
accountViewModel = accountViewModel,
nav = nav,
onDeleteUser = onDeleteUser,
)
}
item {
Spacer(modifier = Modifier.height(30.dp))
}
} }
} }
} }

View File

@@ -45,6 +45,8 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.AlertDialogDefaults import androidx.compose.material3.AlertDialogDefaults
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChip
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -69,6 +71,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@@ -165,16 +168,18 @@ fun FollowSetsManagementDialog(
) { ) {
when (followSetsState) { when (followSetsState) {
is FollowSetFeedState.Loaded -> { is FollowSetFeedState.Loaded -> {
val lists = (followSetsState as FollowSetFeedState.Loaded).feed val sets = (followSetsState as FollowSetFeedState.Loaded).feed
sets.forEachIndexed { index, set ->
lists.forEachIndexed { index, list ->
Spacer(StdVertSpacer) Spacer(StdVertSpacer)
FollowSetItem( FollowSetItem(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
listHeader = list.title, listHeader = set.title,
setVisibility = list.visibility, setVisibility = set.visibility,
userName = userInfo.toBestDisplayName(), userName = userInfo.toBestDisplayName(),
isUserInList = list.profiles.contains(userHex), userIsPrivateMember = set.privateProfiles.contains(userHex),
userIsPublicMember = set.publicProfiles.contains(userHex),
onRemoveUser = { onRemoveUser = {
Log.d( Log.d(
"Amethyst", "Amethyst",
@@ -182,23 +187,30 @@ fun FollowSetsManagementDialog(
) )
followSetsViewModel.removeUserFromSet( followSetsViewModel.removeUserFromSet(
userHex, userHex,
list, set,
account, account,
) )
Log.d( Log.d(
"Amethyst", "Amethyst",
"Updated List. New size: ${list.profiles.size}", "Updated List. Private profiles size: ${set.privateProfiles.size}," +
"Public profiles size: ${set.publicProfiles.size}",
) )
}, },
onAddUser = { onAddUserToList = { userShouldBePrivate ->
Log.d( Log.d(
"Amethyst", "Amethyst",
"ProfileActions: Adding item to list ...", "ProfileActions: Adding item to list ...",
) )
followSetsViewModel.addUserToSet(userHex, list, account) followSetsViewModel.addUserToSet(
userHex,
set,
userShouldBePrivate,
account,
)
Log.d( Log.d(
"Amethyst", "Amethyst",
"Updated List. New size: ${list.profiles.size}", "Updated List. Private profiles size: ${set.privateProfiles.size}," +
"Public profiles size: ${set.publicProfiles.size}",
) )
}, },
) )
@@ -222,11 +234,11 @@ fun FollowSetsManagementDialog(
if (followSetsState != FollowSetFeedState.Loading) { if (followSetsState != FollowSetFeedState.Loading) {
FollowSetsCreationMenu( FollowSetsCreationMenu(
userName = userInfo.toBestDisplayName(), userName = userInfo.toBestDisplayName(),
onSetCreate = { setName, setIsPrivate, description -> onSetCreate = { setName, memberShouldBePrivate, description ->
followSetsViewModel.addFollowSet( followSetsViewModel.addFollowSet(
setName = setName, setName = setName,
setDescription = description, setDescription = description,
isListPrivate = setIsPrivate, firstMemberShouldBePrivate = memberShouldBePrivate,
optionalFirstMemberHex = userHex, optionalFirstMemberHex = userHex,
account = account, account = account,
) )
@@ -306,11 +318,13 @@ fun FollowSetItem(
listHeader: String, listHeader: String,
setVisibility: SetVisibility, setVisibility: SetVisibility,
userName: String, userName: String,
isUserInList: Boolean, userIsPrivateMember: Boolean,
onAddUser: () -> Unit, userIsPublicMember: Boolean,
onAddUserToList: (shouldBePrivateMember: Boolean) -> Unit,
onRemoveUser: () -> Unit, onRemoveUser: () -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val isUserInList = userIsPrivateMember || userIsPublicMember
Row( Row(
modifier = modifier =
modifier modifier
@@ -330,26 +344,10 @@ fun FollowSetItem(
) { ) {
Text(listHeader, fontWeight = FontWeight.Bold) Text(listHeader, fontWeight = FontWeight.Bold)
Spacer(modifier = StdHorzSpacer) Spacer(modifier = StdHorzSpacer)
setVisibility.let { Icon(
val text by derivedStateOf { painter = painterResource(R.drawable.format_list_bulleted_type),
when (it) { contentDescription = stringRes(R.string.follow_set_type_description),
SetVisibility.Public -> stringRes(context, R.string.follow_set_type_public) )
SetVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
SetVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
}
}
Icon(
painter =
painterResource(
when (setVisibility) {
SetVisibility.Public -> R.drawable.ic_public
SetVisibility.Private -> R.drawable.lock
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
},
),
contentDescription = stringRes(R.string.follow_set_type_description, text),
)
}
} }
Spacer(modifier = StdVertSpacer) Spacer(modifier = StdVertSpacer)
@@ -362,7 +360,11 @@ fun FollowSetItem(
Text( Text(
text = text =
if (isUserInList) { if (isUserInList) {
stringRes(R.string.follow_set_presence_indicator, userName) if (userIsPublicMember) {
stringRes(R.string.follow_set_public_presence_indicator, userName)
} else {
stringRes(R.string.follow_set_private_presence_indicator, userName)
}
} else { } else {
stringRes(R.string.follow_set_absence_indicator, userName) stringRes(R.string.follow_set_absence_indicator, userName)
}, },
@@ -389,9 +391,14 @@ fun FollowSetItem(
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
val isUserAddTapped = remember { mutableStateOf(false) }
IconButton( IconButton(
onClick = { onClick = {
if (isUserInList) onRemoveUser() else onAddUser() if (isUserInList) {
onRemoveUser()
} else {
isUserAddTapped.value = true
}
}, },
modifier = modifier =
Modifier Modifier
@@ -423,15 +430,55 @@ fun FollowSetItem(
text = stringRes(if (isUserInList) R.string.remove else R.string.add), text = stringRes(if (isUserInList) R.string.remove else R.string.add),
color = MaterialTheme.colorScheme.onBackground, color = MaterialTheme.colorScheme.onBackground,
) )
UserAdditionOptionsMenu(
isExpanded = isUserAddTapped.value,
onUserAdd = { shouldBePrivateMember ->
onAddUserToList(shouldBePrivateMember)
},
onDismiss = { isUserAddTapped.value = false },
)
} }
} }
} }
@Composable
private fun UserAdditionOptionsMenu(
modifier: Modifier = Modifier,
isExpanded: Boolean,
onUserAdd: (asPrivateMember: Boolean) -> Unit,
onDismiss: () -> Unit,
) {
DropdownMenu(
expanded = isExpanded,
onDismissRequest = onDismiss,
) {
DropdownMenuItem(
text = {
Text(text = "Add as public member")
},
onClick = {
onUserAdd(false)
onDismiss()
},
)
DropdownMenuItem(
text = {
Text(text = "Add as private member")
},
onClick = {
onUserAdd(true)
onDismiss()
},
)
}
}
@Composable @Composable
fun FollowSetsCreationMenu( fun FollowSetsCreationMenu(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
userName: String, userName: String,
onSetCreate: (setName: String, setIsPrivate: Boolean, description: String?) -> Unit, onSetCreate: (setName: String, memberShouldBePrivate: Boolean, description: String?) -> Unit,
) { ) {
val isListAdditionDialogOpen = remember { mutableStateOf(false) } val isListAdditionDialogOpen = remember { mutableStateOf(false) }
val isPrivateOptionTapped = remember { mutableStateOf(false) } val isPrivateOptionTapped = remember { mutableStateOf(false) }
@@ -474,7 +521,6 @@ fun FollowSetsCreationMenu(
isListAdditionDialogOpen.value = false isListAdditionDialogOpen.value = false
isPrivateOptionTapped.value = false isPrivateOptionTapped.value = false
}, },
shouldBePrivate = isPrivateOptionTapped.value,
onCreateList = { name, description -> onCreateList = { name, description ->
onSetCreate(name, isPrivateOptionTapped.value, description) onSetCreate(name, isPrivateOptionTapped.value, description)
}, },
@@ -528,7 +574,11 @@ fun FollowSetCreationItem(
} }
Spacer(modifier = StdVertSpacer) Spacer(modifier = StdVertSpacer)
Text( Text(
stringRes(R.string.follow_set_creation_item_description, setTypeLabel, userName), stringRes(
R.string.follow_set_creation_item_description,
userName,
setTypeLabel.lowercase(Locale.current.platformLocale),
),
fontWeight = FontWeight.Light, fontWeight = FontWeight.Light,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 2, maxLines = 2,

View File

@@ -522,15 +522,18 @@
<string name="follow_set_add_author_from_note_action">Add author to follow set</string> <string name="follow_set_add_author_from_note_action">Add author to follow set</string>
<string name="follow_set_profile_actions_menu_description">Add or remove user from lists, or create a new list with this user.</string> <string name="follow_set_profile_actions_menu_description">Add or remove user from lists, or create a new list with this user.</string>
<string name="follow_set_type_description">Icon for %1$s List</string> <string name="follow_set_type_description">Icon for %1$s List</string>
<string name="follow_set_presence_indicator">"%1$s is present in this list"</string> <string name="follow_set_public_presence_indicator">%1$s is a public member</string>
<string name="follow_set_absence_indicator">"%1$s is not in this list"</string> <string name="follow_set_private_presence_indicator">%1$s is a private member</string>
<string name="follow_set_single_member_label">member</string>
<string name="follow_set_multiple_member_label">members</string>
<string name="follow_set_absence_indicator">%1$s is not in this list</string>
<string name="follow_set_man_dialog_title">Your Follow Sets</string> <string name="follow_set_man_dialog_title">Your Follow Sets</string>
<string name="follow_set_empty_dialog_msg">No follow sets were found, or you don\'t have any follow sets. Tap below to refresh, or use the menu to create one.</string> <string name="follow_set_empty_dialog_msg">No follow sets were found, or you don\'t have any follow sets. Tap below to refresh, or use the menu to create one.</string>
<string name="follow_set_error_dialog_msg">There was a problem while fetching: %1$s</string> <string name="follow_set_error_dialog_msg">There was a problem while fetching: %1$s</string>
<string name="follow_set_creation_menu_title">Make New List</string> <string name="follow_set_creation_menu_title">Make New List</string>
<string name="follow_set_creation_item_label">"Create new %1$s list with user</string> <string name="follow_set_creation_item_label">New list with %1$s membership</string>
<string name="follow_set_creation_item_description">Creates a %1$s follow set, and adds %2$s to it.</string> <string name="follow_set_creation_item_description">Creates a new follow set, and adds %1$s as a %2$s member.</string>
<string name="follow_set_creation_dialog_title">New %1$s List</string> <string name="follow_set_creation_dialog_title">New Follow Set</string>
<string name="follow_set_creation_name_label">Set name</string> <string name="follow_set_creation_name_label">Set name</string>
<string name="follow_set_creation_desc_label">Set description(optional)</string> <string name="follow_set_creation_desc_label">Set description(optional)</string>
<string name="follow_set_creation_action_btn_label">Create set</string> <string name="follow_set_creation_action_btn_label">Create set</string>