mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 21:46:45 +01:00
Refactor UI to adapt to correct way of managing follow sets.
This commit is contained in:
@@ -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 = {
|
|
||||||
isPrivateOptionTapped.value = true
|
|
||||||
isSetAdditionDialogOpen.value = true
|
|
||||||
},
|
},
|
||||||
shape = CircleShape,
|
icon = {
|
||||||
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 =
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -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,11 +91,12 @@ fun CustomSetItem(
|
|||||||
) {
|
) {
|
||||||
Text(followSet.title, fontWeight = FontWeight.Bold)
|
Text(followSet.title, fontWeight = FontWeight.Bold)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
|
if (followSet.publicProfiles.isEmpty() && followSet.privateProfiles.isEmpty()) {
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = true,
|
selected = true,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
label = {
|
label = {
|
||||||
Text(text = "${followSet.profiles.size}")
|
Text(text = "No members")
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -111,6 +107,60 @@ fun CustomSetItem(
|
|||||||
shape = ButtonBorder,
|
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(
|
||||||
|
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(
|
||||||
followSet.description ?: "",
|
followSet.description ?: "",
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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,7 +271,24 @@ private fun FollowSetListView(
|
|||||||
contentPadding = FeedPadding,
|
contentPadding = FeedPadding,
|
||||||
state = listState,
|
state = listState,
|
||||||
) {
|
) {
|
||||||
itemsIndexed(followSetList, key = { _, item -> item.pubkeyHex }) { _, item ->
|
if (publicMemberList.isNotEmpty()) {
|
||||||
|
stickyHeader {
|
||||||
|
Column(
|
||||||
|
modifier = VertPadding,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
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(
|
FollowSetListItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
user = item,
|
user = item,
|
||||||
@@ -281,6 +297,36 @@ private fun FollowSetListView(
|
|||||||
onDeleteUser = onDeleteUser,
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,27 +344,11 @@ fun FollowSetItem(
|
|||||||
) {
|
) {
|
||||||
Text(listHeader, fontWeight = FontWeight.Bold)
|
Text(listHeader, fontWeight = FontWeight.Bold)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
setVisibility.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Icon(
|
Icon(
|
||||||
painter =
|
painter = painterResource(R.drawable.format_list_bulleted_type),
|
||||||
painterResource(
|
contentDescription = stringRes(R.string.follow_set_type_description),
|
||||||
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)
|
||||||
Row {
|
Row {
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user