Merge pull request #1507 from KotlinGeekDev/followset-improvements
More follow set improvements.
This commit is contained in:
+8
-11
@@ -31,9 +31,10 @@ data class FollowSet(
|
|||||||
val identifierTag: String,
|
val identifierTag: String,
|
||||||
val title: String,
|
val title: String,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val visibility: SetVisibility,
|
val visibility: SetVisibility = SetVisibility.Mixed,
|
||||||
val profiles: Set<String>,
|
val privateProfiles: Set<String> = emptySet(),
|
||||||
) : NostrSet(setVisibility = visibility, content = profiles) {
|
val publicProfiles: Set<String> = emptySet(),
|
||||||
|
) : NostrSet(setVisibility = visibility, privateContent = privateProfiles, publicContent = publicProfiles) {
|
||||||
companion object {
|
companion object {
|
||||||
fun mapEventToSet(
|
fun mapEventToSet(
|
||||||
event: PeopleListEvent,
|
event: PeopleListEvent,
|
||||||
@@ -53,26 +54,22 @@ data class FollowSet(
|
|||||||
identifierTag = dTag,
|
identifierTag = dTag,
|
||||||
title = listTitle,
|
title = listTitle,
|
||||||
description = listDescription,
|
description = listDescription,
|
||||||
visibility = SetVisibility.Private,
|
privateProfiles = privateFollows.toSet(),
|
||||||
profiles = privateFollows.toSet(),
|
|
||||||
)
|
)
|
||||||
} else if (publicFollows.isNotEmpty() && privateFollows.isEmpty()) {
|
} else if (publicFollows.isNotEmpty() && privateFollows.isEmpty()) {
|
||||||
FollowSet(
|
FollowSet(
|
||||||
identifierTag = dTag,
|
identifierTag = dTag,
|
||||||
title = listTitle,
|
title = listTitle,
|
||||||
description = listDescription,
|
description = listDescription,
|
||||||
visibility = SetVisibility.Public,
|
publicProfiles = publicFollows.toSet(),
|
||||||
profiles = publicFollows.toSet(),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Follow set is empty, so assume public. Why? Nostr limitation.
|
|
||||||
// TODO: Could this be fixed at protocol level?
|
|
||||||
FollowSet(
|
FollowSet(
|
||||||
identifierTag = dTag,
|
identifierTag = dTag,
|
||||||
title = listTitle,
|
title = listTitle,
|
||||||
description = listDescription,
|
description = listDescription,
|
||||||
visibility = SetVisibility.Public,
|
privateProfiles = privateFollows.toSet(),
|
||||||
profiles = publicFollows.toSet(),
|
publicProfiles = publicFollows.toSet(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-16
@@ -25,20 +25,16 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.catch
|
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onCompletion
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class FollowSetState(
|
class FollowSetState(
|
||||||
@@ -50,7 +46,7 @@ class FollowSetState(
|
|||||||
private val isActive = MutableStateFlow(false)
|
private val isActive = MutableStateFlow(false)
|
||||||
|
|
||||||
suspend fun getFollowSetNotes() =
|
suspend fun getFollowSetNotes() =
|
||||||
withContext(Dispatchers.Default) {
|
withContext(Dispatchers.IO) {
|
||||||
val followSetNotes = LocalCache.getFollowSetNotesFor(user)
|
val followSetNotes = LocalCache.getFollowSetNotesFor(user)
|
||||||
return@withContext followSetNotes
|
return@withContext followSetNotes
|
||||||
}
|
}
|
||||||
@@ -63,12 +59,12 @@ class FollowSetState(
|
|||||||
emit(followSets)
|
emit(followSets)
|
||||||
delay(2000)
|
delay(2000)
|
||||||
}
|
}
|
||||||
}.flowOn(Dispatchers.Default)
|
}.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
val profilesFlow =
|
val profilesFlow =
|
||||||
getFollowSetNotesFlow()
|
getFollowSetNotesFlow()
|
||||||
.map { it ->
|
.map { it ->
|
||||||
it.flatMapTo(mutableSetOf()) { it.profiles }.toSet()
|
it.flatMapTo(mutableSetOf()) { it.privateProfiles + it.publicProfiles }.toSet()
|
||||||
}.stateIn(scope, SharingStarted.Eagerly, emptySet())
|
}.stateIn(scope, SharingStarted.Eagerly, emptySet())
|
||||||
|
|
||||||
fun mapNoteToFollowSet(note: Note): FollowSet =
|
fun mapNoteToFollowSet(note: Note): FollowSet =
|
||||||
@@ -82,14 +78,5 @@ class FollowSetState(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
isActive.update { true }
|
isActive.update { true }
|
||||||
scope.launch(Dispatchers.Default) {
|
|
||||||
getFollowSetNotesFlow()
|
|
||||||
.onCompletion {
|
|
||||||
isActive.update { false }
|
|
||||||
}.catch {
|
|
||||||
Log.e(this@FollowSetState.javaClass.simpleName, "Error on flow collection: ${it.message}")
|
|
||||||
isActive.update { false }
|
|
||||||
}.collect {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -22,11 +22,13 @@ package com.vitorpamplona.amethyst.model.nip51Lists.followSets
|
|||||||
|
|
||||||
sealed class NostrSet(
|
sealed class NostrSet(
|
||||||
val setVisibility: SetVisibility,
|
val setVisibility: SetVisibility,
|
||||||
val content: Collection<String>,
|
val privateContent: Collection<String>,
|
||||||
|
val publicContent: Collection<String>,
|
||||||
)
|
)
|
||||||
|
|
||||||
class CuratedBookmarkSet(
|
class CuratedBookmarkSet(
|
||||||
val name: String,
|
val name: String,
|
||||||
val visibility: SetVisibility,
|
val visibility: SetVisibility,
|
||||||
val setItems: List<String>,
|
val privateSetItems: List<String>,
|
||||||
) : NostrSet(visibility, setItems)
|
val publicSetItems: List<String>,
|
||||||
|
) : NostrSet(visibility, privateSetItems, publicSetItems)
|
||||||
|
|||||||
+52
-80
@@ -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,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -138,6 +137,21 @@ fun ListsAndSetsScreen(
|
|||||||
account = accountViewModel.account,
|
account = accountViewModel.account,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
changeItemDescription = { followSet, newDescription ->
|
||||||
|
followSetsViewModel.modifyFollowSetDescription(
|
||||||
|
newDescription = newDescription,
|
||||||
|
followSet = followSet,
|
||||||
|
account = accountViewModel.account,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cloneItem = { followSet, customName, customDescription ->
|
||||||
|
followSetsViewModel.cloneFollowSet(
|
||||||
|
currentFollowSet = followSet,
|
||||||
|
customCloneName = customName,
|
||||||
|
customCloneDescription = customDescription,
|
||||||
|
account = accountViewModel.account,
|
||||||
|
)
|
||||||
|
},
|
||||||
deleteItem = { followSet ->
|
deleteItem = { followSet ->
|
||||||
followSetsViewModel.deleteFollowSet(
|
followSetsViewModel.deleteFollowSet(
|
||||||
followSet = followSet,
|
followSet = followSet,
|
||||||
@@ -153,14 +167,16 @@ 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,
|
||||||
|
changeItemDescription: (followSet: FollowSet, newDescription: String?) -> Unit,
|
||||||
|
cloneItem: (followSet: FollowSet, customName: String?, customDesc: 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 +201,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)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -222,11 +230,13 @@ fun CustomListsScreen(
|
|||||||
onRefresh = refresh,
|
onRefresh = refresh,
|
||||||
onOpenItem = openItem,
|
onOpenItem = openItem,
|
||||||
onRenameItem = renameItem,
|
onRenameItem = renameItem,
|
||||||
|
onItemDescriptionChange = changeItemDescription,
|
||||||
|
onItemClone = cloneItem,
|
||||||
onDeleteItem = deleteItem,
|
onDeleteItem = deleteItem,
|
||||||
)
|
)
|
||||||
|
|
||||||
1 -> LabeledBookmarksFeedView()
|
1 -> LabeledBookmarksFeedView()
|
||||||
2 -> GeneralBookmarksFeedView()
|
// 2 -> GeneralBookmarksFeedView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,57 +246,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 = stringRes(R.string.follow_set_create_btn_label))
|
||||||
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 +283,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 +295,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 +313,7 @@ fun NewSetCreationDialog(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
// For the list description
|
// For the set description
|
||||||
TextField(
|
TextField(
|
||||||
value =
|
value =
|
||||||
(
|
(
|
||||||
@@ -425,6 +391,12 @@ private fun SetItemPreview() {
|
|||||||
onFollowSetRename = {
|
onFollowSetRename = {
|
||||||
println("Follow set new name: $it")
|
println("Follow set new name: $it")
|
||||||
},
|
},
|
||||||
|
onFollowSetDescriptionChange = { description ->
|
||||||
|
println("The follow set's description has been changed to $description")
|
||||||
|
},
|
||||||
|
onFollowSetClone = { newName, newDesc ->
|
||||||
|
println("The follow set has been cloned, and has custom name: $newName, Desc: $newDesc")
|
||||||
|
},
|
||||||
onFollowSetDelete = {
|
onFollowSetDelete = {
|
||||||
println(" The follow set ${sampleFollowSet.title} has been deleted.")
|
println(" The follow set ${sampleFollowSet.title} has been deleted.")
|
||||||
},
|
},
|
||||||
|
|||||||
+285
-65
@@ -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,15 +53,14 @@ 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
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
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(
|
||||||
@@ -72,6 +68,8 @@ fun CustomSetItem(
|
|||||||
followSet: FollowSet,
|
followSet: FollowSet,
|
||||||
onFollowSetClick: () -> Unit,
|
onFollowSetClick: () -> Unit,
|
||||||
onFollowSetRename: (String) -> Unit,
|
onFollowSetRename: (String) -> Unit,
|
||||||
|
onFollowSetDescriptionChange: (String?) -> Unit,
|
||||||
|
onFollowSetClone: (customName: String?, customDescription: String?) -> Unit,
|
||||||
onFollowSetDelete: () -> Unit,
|
onFollowSetDelete: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -96,20 +94,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 = stringRes(R.string.follow_set_empty_label))
|
||||||
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")
|
||||||
|
},
|
||||||
|
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")
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.lock),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
shape = ButtonBorder,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = StdVertSpacer)
|
Spacer(modifier = StdVertSpacer)
|
||||||
Text(
|
Text(
|
||||||
@@ -119,34 +172,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(
|
||||||
@@ -159,7 +184,10 @@ fun CustomSetItem(
|
|||||||
) {
|
) {
|
||||||
SetOptionsButton(
|
SetOptionsButton(
|
||||||
followSetName = followSet.title,
|
followSetName = followSet.title,
|
||||||
onListRename = onFollowSetRename,
|
followSetDescription = followSet.description,
|
||||||
|
onSetRename = onFollowSetRename,
|
||||||
|
onSetDescriptionChange = onFollowSetDescriptionChange,
|
||||||
|
onSetCloneCreate = onFollowSetClone,
|
||||||
onListDelete = onFollowSetDelete,
|
onListDelete = onFollowSetDelete,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -167,10 +195,13 @@ fun CustomSetItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SetOptionsButton(
|
private fun SetOptionsButton(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
followSetName: String,
|
followSetName: String,
|
||||||
onListRename: (String) -> Unit,
|
followSetDescription: String?,
|
||||||
|
onSetRename: (String) -> Unit,
|
||||||
|
onSetDescriptionChange: (String?) -> Unit,
|
||||||
|
onSetCloneCreate: (optionalName: String?, optionalDec: String?) -> Unit,
|
||||||
onListDelete: () -> Unit,
|
onListDelete: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val isMenuOpen = remember { mutableStateOf(false) }
|
val isMenuOpen = remember { mutableStateOf(false) }
|
||||||
@@ -181,10 +212,13 @@ fun SetOptionsButton(
|
|||||||
VerticalDotsIcon()
|
VerticalDotsIcon()
|
||||||
|
|
||||||
SetOptionsMenu(
|
SetOptionsMenu(
|
||||||
listName = followSetName,
|
setName = followSetName,
|
||||||
|
setDescription = followSetDescription,
|
||||||
isExpanded = isMenuOpen.value,
|
isExpanded = isMenuOpen.value,
|
||||||
onDismiss = { isMenuOpen.value = false },
|
onDismiss = { isMenuOpen.value = false },
|
||||||
onListRename = onListRename,
|
onSetRename = onSetRename,
|
||||||
|
onSetDescriptionChange = onSetDescriptionChange,
|
||||||
|
onSetClone = onSetCloneCreate,
|
||||||
onDelete = onListDelete,
|
onDelete = onListDelete,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -194,26 +228,27 @@ fun SetOptionsButton(
|
|||||||
private fun SetOptionsMenu(
|
private fun SetOptionsMenu(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isExpanded: Boolean,
|
isExpanded: Boolean,
|
||||||
listName: String,
|
setName: String,
|
||||||
onListRename: (String) -> Unit,
|
setDescription: String?,
|
||||||
|
onSetRename: (String) -> Unit,
|
||||||
|
onSetDescriptionChange: (String?) -> Unit,
|
||||||
|
onSetClone: (optionalNewName: String?, optionalNewDesc: String?) -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val isRenameDialogOpen = remember { mutableStateOf(false) }
|
val isRenameDialogOpen = remember { mutableStateOf(false) }
|
||||||
val renameString = remember { mutableStateOf("") }
|
val renameString = remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val isDescriptionModDialogOpen = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val isCopyDialogOpen = remember { mutableStateOf(false) }
|
||||||
|
val optionalCloneName = remember { mutableStateOf<String?>(null) }
|
||||||
|
val optionalCloneDescription = remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = isExpanded,
|
expanded = isExpanded,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
|
||||||
text = {
|
|
||||||
Text(text = stringRes(R.string.quick_action_delete))
|
|
||||||
},
|
|
||||||
onClick = {
|
|
||||||
onDelete()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = {
|
text = {
|
||||||
Text(text = stringRes(R.string.follow_set_rename_btn_label))
|
Text(text = stringRes(R.string.follow_set_rename_btn_label))
|
||||||
@@ -223,25 +258,76 @@ private fun SetOptionsMenu(
|
|||||||
onDismiss()
|
onDismiss()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = stringRes(R.string.follow_set_desc_modify_label))
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
isDescriptionModDialogOpen.value = true
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = stringRes(R.string.follow_set_copy_action_btn_label))
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
isCopyDialogOpen.value = true
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = stringRes(R.string.quick_action_delete))
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onDelete()
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isRenameDialogOpen.value) {
|
if (isRenameDialogOpen.value) {
|
||||||
RenameDialog(
|
SetRenameDialog(
|
||||||
currentName = listName,
|
currentName = setName,
|
||||||
newName = renameString.value,
|
newName = renameString.value,
|
||||||
onStringRenameChange = {
|
onStringRenameChange = {
|
||||||
renameString.value = it
|
renameString.value = it
|
||||||
},
|
},
|
||||||
onDismissDialog = { isRenameDialogOpen.value = false },
|
onDismissDialog = { isRenameDialogOpen.value = false },
|
||||||
onListRename = {
|
onListRename = {
|
||||||
onListRename(renameString.value)
|
onSetRename(renameString.value)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isDescriptionModDialogOpen.value) {
|
||||||
|
SetModifyDescriptionDialog(
|
||||||
|
currentDescription = setDescription,
|
||||||
|
onDismissDialog = { isDescriptionModDialogOpen.value = false },
|
||||||
|
onModifyDescription = onSetDescriptionChange,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCopyDialogOpen.value) {
|
||||||
|
SetCloneDialog(
|
||||||
|
optionalNewName = optionalCloneName.value,
|
||||||
|
optionalNewDesc = optionalCloneDescription.value,
|
||||||
|
onCloneNameChange = {
|
||||||
|
optionalCloneName.value = it
|
||||||
|
},
|
||||||
|
onCloneDescChange = {
|
||||||
|
optionalCloneDescription.value = it
|
||||||
|
},
|
||||||
|
onCloneCreate = { name, description ->
|
||||||
|
onSetClone(optionalCloneName.value, optionalCloneDescription.value)
|
||||||
|
},
|
||||||
|
onDismiss = { isCopyDialogOpen.value = false },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenameDialog(
|
private fun SetRenameDialog(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
currentName: String,
|
currentName: String,
|
||||||
newName: String,
|
newName: String,
|
||||||
@@ -299,3 +385,137 @@ private fun RenameDialog(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SetModifyDescriptionDialog(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
currentDescription: String?,
|
||||||
|
onDismissDialog: () -> Unit,
|
||||||
|
onModifyDescription: (String?) -> Unit,
|
||||||
|
) {
|
||||||
|
val updatedDescription = remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
val modifyIndicatorLabel =
|
||||||
|
if (currentDescription == null) {
|
||||||
|
stringRes(R.string.follow_set_empty_desc_label)
|
||||||
|
} else {
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(stringRes(R.string.follow_set_current_desc_label) + " ")
|
||||||
|
withStyle(
|
||||||
|
SpanStyle(
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontStyle = FontStyle.Normal,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
append("\"" + currentDescription + "\"")
|
||||||
|
}
|
||||||
|
}.text
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissDialog,
|
||||||
|
title = {
|
||||||
|
Text(text = stringRes(R.string.follow_set_desc_modify_label))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = modifyIndicatorLabel,
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
)
|
||||||
|
TextField(
|
||||||
|
value = updatedDescription.value ?: "",
|
||||||
|
onValueChange = { updatedDescription.value = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onModifyDescription(updatedDescription.value)
|
||||||
|
onDismissDialog()
|
||||||
|
},
|
||||||
|
) { Text(text = stringRes(R.string.follow_set_desc_modify_btn_label)) }
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
Button(onClick = onDismissDialog) { Text(text = stringRes(R.string.cancel)) }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SetCloneDialog(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
optionalNewName: String?,
|
||||||
|
optionalNewDesc: String?,
|
||||||
|
onCloneNameChange: (String?) -> Unit,
|
||||||
|
onCloneDescChange: (String?) -> Unit,
|
||||||
|
onCloneCreate: (customName: String?, customDescription: String?) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.follow_set_copy_dialog_title),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.follow_set_copy_indicator_description),
|
||||||
|
fontSize = 15.sp,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
)
|
||||||
|
// For the set clone name
|
||||||
|
TextField(
|
||||||
|
value = optionalNewName ?: "",
|
||||||
|
onValueChange = onCloneNameChange,
|
||||||
|
label = {
|
||||||
|
Text(text = stringRes(R.string.follow_set_copy_name_label))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
|
// For the set clone description
|
||||||
|
TextField(
|
||||||
|
value = optionalNewDesc ?: "",
|
||||||
|
onValueChange = onCloneDescChange,
|
||||||
|
label = {
|
||||||
|
Text(text = stringRes(R.string.follow_set_copy_desc_label))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onCloneCreate(optionalNewName, optionalNewDesc)
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(stringRes(R.string.follow_set_copy_action_btn_label))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
Button(
|
||||||
|
onClick = onDismiss,
|
||||||
|
) {
|
||||||
|
Text(stringRes(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
+12
@@ -51,6 +51,8 @@ fun FollowSetFeedView(
|
|||||||
onRefresh: () -> Unit = {},
|
onRefresh: () -> Unit = {},
|
||||||
onOpenItem: (String) -> Unit = {},
|
onOpenItem: (String) -> Unit = {},
|
||||||
onRenameItem: (targetSet: FollowSet, newName: String) -> Unit,
|
onRenameItem: (targetSet: FollowSet, newName: String) -> Unit,
|
||||||
|
onItemDescriptionChange: (followSet: FollowSet, newDescription: String?) -> Unit,
|
||||||
|
onItemClone: (followSet: FollowSet, customName: String?, customDesc: String?) -> Unit,
|
||||||
onDeleteItem: (followSet: FollowSet) -> Unit,
|
onDeleteItem: (followSet: FollowSet) -> Unit,
|
||||||
) {
|
) {
|
||||||
when (followSetFeedState) {
|
when (followSetFeedState) {
|
||||||
@@ -63,6 +65,8 @@ fun FollowSetFeedView(
|
|||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
onItemClick = onOpenItem,
|
onItemClick = onOpenItem,
|
||||||
onItemRename = onRenameItem,
|
onItemRename = onRenameItem,
|
||||||
|
onItemDescriptionChange = onItemDescriptionChange,
|
||||||
|
onItemClone = onItemClone,
|
||||||
onItemDelete = onDeleteItem,
|
onItemDelete = onDeleteItem,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -91,6 +95,8 @@ fun FollowSetLoaded(
|
|||||||
onRefresh: () -> Unit = {},
|
onRefresh: () -> Unit = {},
|
||||||
onItemClick: (itemIdentifier: String) -> Unit = {},
|
onItemClick: (itemIdentifier: String) -> Unit = {},
|
||||||
onItemRename: (followSet: FollowSet, newName: String) -> Unit,
|
onItemRename: (followSet: FollowSet, newName: String) -> Unit,
|
||||||
|
onItemDescriptionChange: (followSet: FollowSet, newDescription: String?) -> Unit,
|
||||||
|
onItemClone: (followSet: FollowSet, customName: String?, customDesc: String?) -> Unit,
|
||||||
onItemDelete: (followSet: FollowSet) -> Unit,
|
onItemDelete: (followSet: FollowSet) -> Unit,
|
||||||
) {
|
) {
|
||||||
Log.d("FollowSetComposable", "FollowSetLoaded: Follow Set size: ${loadedFeedState.size}")
|
Log.d("FollowSetComposable", "FollowSetLoaded: Follow Set size: ${loadedFeedState.size}")
|
||||||
@@ -113,6 +119,12 @@ fun FollowSetLoaded(
|
|||||||
onFollowSetRename = {
|
onFollowSetRename = {
|
||||||
onItemRename(set, it)
|
onItemRename(set, it)
|
||||||
},
|
},
|
||||||
|
onFollowSetDescriptionChange = { newDescription ->
|
||||||
|
onItemDescriptionChange(set, newDescription)
|
||||||
|
},
|
||||||
|
onFollowSetClone = { cloneName, cloneDescription ->
|
||||||
|
onItemClone(set, cloneName, cloneDescription)
|
||||||
|
},
|
||||||
onFollowSetDelete = {
|
onFollowSetDelete = {
|
||||||
onItemDelete(set)
|
onItemDelete(set)
|
||||||
},
|
},
|
||||||
|
|||||||
+53
-5
@@ -31,7 +31,6 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
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.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
|
||||||
@@ -117,8 +116,8 @@ class FollowSetFeedViewModel(
|
|||||||
fun addFollowSet(
|
fun addFollowSet(
|
||||||
setName: String,
|
setName: String,
|
||||||
setDescription: String?,
|
setDescription: String?,
|
||||||
isListPrivate: Boolean,
|
|
||||||
optionalFirstMemberHex: String? = null,
|
optionalFirstMemberHex: String? = null,
|
||||||
|
firstMemberShouldBePrivate: Boolean = false,
|
||||||
account: Account,
|
account: Account,
|
||||||
) {
|
) {
|
||||||
if (!account.settings.isWriteable()) {
|
if (!account.settings.isWriteable()) {
|
||||||
@@ -129,8 +128,9 @@ class FollowSetFeedViewModel(
|
|||||||
dTag = UUID.randomUUID().toString(),
|
dTag = UUID.randomUUID().toString(),
|
||||||
title = setName,
|
title = setName,
|
||||||
description = setDescription,
|
description = setDescription,
|
||||||
isPrivate = isListPrivate,
|
isPrivate = firstMemberShouldBePrivate,
|
||||||
firstMemberHex = optionalFirstMemberHex,
|
firstPublicMembers = if (optionalFirstMemberHex != null) listOf(optionalFirstMemberHex) else emptyList(),
|
||||||
|
firstPrivateMembers = if (optionalFirstMemberHex != null) listOf(optionalFirstMemberHex) else emptyList(),
|
||||||
signer = account.signer,
|
signer = account.signer,
|
||||||
) {
|
) {
|
||||||
account.sendMyPublicAndPrivateOutbox(it)
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
@@ -160,6 +160,51 @@ class FollowSetFeedViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun modifyFollowSetDescription(
|
||||||
|
newDescription: String?,
|
||||||
|
followSet: FollowSet,
|
||||||
|
account: Account,
|
||||||
|
) {
|
||||||
|
if (!account.settings.isWriteable()) {
|
||||||
|
println("You are in read-only mode. Please login to make modifications.")
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val setEvent = getFollowSetNote(followSet.identifierTag, account)?.event as PeopleListEvent
|
||||||
|
PeopleListEvent.modifyDescription(
|
||||||
|
earlierVersion = setEvent,
|
||||||
|
newDescription = newDescription,
|
||||||
|
signer = account.signer,
|
||||||
|
) {
|
||||||
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cloneFollowSet(
|
||||||
|
currentFollowSet: FollowSet,
|
||||||
|
customCloneName: String?,
|
||||||
|
customCloneDescription: String?,
|
||||||
|
account: Account,
|
||||||
|
) {
|
||||||
|
if (!account.settings.isWriteable()) {
|
||||||
|
println("You are in read-only mode. Please login to make modifications.")
|
||||||
|
} else {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
PeopleListEvent.copy(
|
||||||
|
dTag = UUID.randomUUID().toString(),
|
||||||
|
title = customCloneName ?: currentFollowSet.title,
|
||||||
|
description = customCloneDescription ?: currentFollowSet.description,
|
||||||
|
firstPublicMembers = currentFollowSet.publicProfiles.toList(),
|
||||||
|
firstPrivateMembers = currentFollowSet.privateProfiles.toList(),
|
||||||
|
signer = account.signer,
|
||||||
|
) {
|
||||||
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun deleteFollowSet(
|
fun deleteFollowSet(
|
||||||
followSet: FollowSet,
|
followSet: FollowSet,
|
||||||
account: Account,
|
account: Account,
|
||||||
@@ -179,6 +224,7 @@ class FollowSetFeedViewModel(
|
|||||||
fun addUserToSet(
|
fun addUserToSet(
|
||||||
userProfileHex: String,
|
userProfileHex: String,
|
||||||
followSet: FollowSet,
|
followSet: FollowSet,
|
||||||
|
shouldBePrivateMember: Boolean,
|
||||||
account: Account,
|
account: Account,
|
||||||
) {
|
) {
|
||||||
if (!account.settings.isWriteable()) {
|
if (!account.settings.isWriteable()) {
|
||||||
@@ -190,7 +236,7 @@ class FollowSetFeedViewModel(
|
|||||||
PeopleListEvent.addUser(
|
PeopleListEvent.addUser(
|
||||||
earlierVersion = followSetEvent,
|
earlierVersion = followSetEvent,
|
||||||
pubKeyHex = userProfileHex,
|
pubKeyHex = userProfileHex,
|
||||||
isPrivate = followSet.visibility == SetVisibility.Private,
|
isPrivate = shouldBePrivateMember,
|
||||||
signer = account.signer,
|
signer = account.signer,
|
||||||
) {
|
) {
|
||||||
account.sendMyPublicAndPrivateOutbox(it)
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
@@ -201,6 +247,7 @@ class FollowSetFeedViewModel(
|
|||||||
|
|
||||||
fun removeUserFromSet(
|
fun removeUserFromSet(
|
||||||
userProfileHex: String,
|
userProfileHex: String,
|
||||||
|
userIsPrivate: Boolean,
|
||||||
followSet: FollowSet,
|
followSet: FollowSet,
|
||||||
account: Account,
|
account: Account,
|
||||||
) {
|
) {
|
||||||
@@ -213,6 +260,7 @@ class FollowSetFeedViewModel(
|
|||||||
PeopleListEvent.removeUser(
|
PeopleListEvent.removeUser(
|
||||||
earlierVersion = followSetEvent,
|
earlierVersion = followSetEvent,
|
||||||
pubKeyHex = userProfileHex,
|
pubKeyHex = userProfileHex,
|
||||||
|
isUserPrivate = userIsPrivate,
|
||||||
signer = account.signer,
|
signer = account.signer,
|
||||||
) {
|
) {
|
||||||
account.sendMyPublicAndPrivateOutbox(it)
|
account.sendMyPublicAndPrivateOutbox(it)
|
||||||
|
|||||||
+68
-20
@@ -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
|
||||||
@@ -76,11 +77,13 @@ import com.vitorpamplona.amethyst.ui.note.VerticalDotsIcon
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSetFeedViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
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 +147,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 +175,7 @@ fun FollowSetScreen(
|
|||||||
selectedSet,
|
selectedSet,
|
||||||
accountViewModel.account,
|
accountViewModel.account,
|
||||||
)
|
)
|
||||||
|
navigator.popBack()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -190,10 +195,12 @@ 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,
|
||||||
|
userIsPrivate = selectedSet.privateProfiles.contains(it),
|
||||||
selectedSet,
|
selectedSet,
|
||||||
accountViewModel.account,
|
accountViewModel.account,
|
||||||
)
|
)
|
||||||
@@ -233,14 +240,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 +260,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 +273,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 = stringRes(R.string.follow_set_public_members_header_label),
|
||||||
)
|
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 = stringRes(R.string.follow_set_private_members_header_label),
|
||||||
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+90
-39
@@ -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,31 @@ fun FollowSetsManagementDialog(
|
|||||||
)
|
)
|
||||||
followSetsViewModel.removeUserFromSet(
|
followSetsViewModel.removeUserFromSet(
|
||||||
userHex,
|
userHex,
|
||||||
list,
|
userIsPrivate = set.privateProfiles.contains(userHex),
|
||||||
|
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 +235,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 +319,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 +345,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_icon_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 +361,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 +392,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 +431,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 = stringRes(R.string.follow_set_public_member_add_label))
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
onUserAdd(false)
|
||||||
|
onDismiss()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = {
|
||||||
|
Text(text = stringRes(R.string.follow_set_private_member_add_label))
|
||||||
|
},
|
||||||
|
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 +522,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 +575,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,
|
||||||
|
|||||||
@@ -453,8 +453,7 @@
|
|||||||
<string name="follow_set_empty_feed_msg">Zdá se, že zatím nemáte žádné sady sledování.\nKlepněte níže pro obnovení nebo použijte tlačítko přidat k vytvoření nové.</string>
|
<string name="follow_set_empty_feed_msg">Zdá se, že zatím nemáte žádné sady sledování.\nKlepněte níže pro obnovení nebo použijte tlačítko přidat k vytvoření nové.</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Přidat autora do sady sledování</string>
|
<string name="follow_set_add_author_from_note_action">Přidat autora do sady sledování</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Přidat nebo odebrat uživatele ze seznamů, nebo vytvořit nový seznam s tímto uživatelem.</string>
|
<string name="follow_set_profile_actions_menu_description">Přidat nebo odebrat uživatele ze seznamů, nebo vytvořit nový seznam s tímto uživatelem.</string>
|
||||||
<string name="follow_set_type_description">Ikona pro seznam %1$s</string>
|
<string name="follow_set_icon_description">Ikona pro seznam %1$s</string>
|
||||||
<string name="follow_set_presence_indicator">"%1$s je v tomto seznamu"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"%1$s není v tomto seznamu"</string>
|
<string name="follow_set_absence_indicator">"%1$s není v tomto seznamu"</string>
|
||||||
<string name="follow_set_man_dialog_title">Vaše sady sledování</string>
|
<string name="follow_set_man_dialog_title">Vaše sady sledování</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Nebyly nalezeny žádné sady sledování, nebo žádné nemáte. Klepněte níže pro obnovení nebo použijte menu pro vytvoření nové.</string>
|
<string name="follow_set_empty_dialog_msg">Nebyly nalezeny žádné sady sledování, nebo žádné nemáte. Klepněte níže pro obnovení nebo použijte menu pro vytvoření nové.</string>
|
||||||
|
|||||||
@@ -961,8 +961,7 @@
|
|||||||
<string name="follow_set_empty_feed_msg">Zdá se, že zatím nemáte žádné sady sledování.\nKlepněte níže pro obnovení nebo použijte tlačítko přidat k vytvoření nové.</string>
|
<string name="follow_set_empty_feed_msg">Zdá se, že zatím nemáte žádné sady sledování.\nKlepněte níže pro obnovení nebo použijte tlačítko přidat k vytvoření nové.</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Přidat autora do sady sledování</string>
|
<string name="follow_set_add_author_from_note_action">Přidat autora do sady sledování</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Přidat nebo odebrat uživatele ze seznamů, nebo vytvořit nový seznam s tímto uživatelem.</string>
|
<string name="follow_set_profile_actions_menu_description">Přidat nebo odebrat uživatele ze seznamů, nebo vytvořit nový seznam s tímto uživatelem.</string>
|
||||||
<string name="follow_set_type_description">Ikona pro seznam %1$s</string>
|
<string name="follow_set_icon_description">Ikona pro seznam %1$s</string>
|
||||||
<string name="follow_set_presence_indicator">%1$s je v tomto seznamu</string>
|
|
||||||
<string name="follow_set_absence_indicator">%1$s není v tomto seznamu</string>
|
<string name="follow_set_absence_indicator">%1$s není v tomto seznamu</string>
|
||||||
<string name="follow_set_man_dialog_title">Vaše sady sledování</string>
|
<string name="follow_set_man_dialog_title">Vaše sady sledování</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Nebyly nalezeny žádné sady sledování, nebo žádné nemáte. Klepněte níže pro obnovení nebo použijte menu pro vytvoření nové.</string>
|
<string name="follow_set_empty_dialog_msg">Nebyly nalezeny žádné sady sledování, nebo žádné nemáte. Klepněte níže pro obnovení nebo použijte menu pro vytvoření nové.</string>
|
||||||
|
|||||||
@@ -459,8 +459,7 @@ anz der Bedingungen ist erforderlich</string>
|
|||||||
<string name="follow_set_empty_feed_msg">Es scheint, dass du noch keine Folge-Sets hast.\nTippe unten zum Aktualisieren oder verwende die Plus-Taste, um ein neues zu erstellen.</string>
|
<string name="follow_set_empty_feed_msg">Es scheint, dass du noch keine Folge-Sets hast.\nTippe unten zum Aktualisieren oder verwende die Plus-Taste, um ein neues zu erstellen.</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Autor zum Folge-Set hinzufügen</string>
|
<string name="follow_set_add_author_from_note_action">Autor zum Folge-Set hinzufügen</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Benutzer zu Listen hinzufügen oder entfernen, oder eine neue Liste mit diesem Benutzer erstellen.</string>
|
<string name="follow_set_profile_actions_menu_description">Benutzer zu Listen hinzufügen oder entfernen, oder eine neue Liste mit diesem Benutzer erstellen.</string>
|
||||||
<string name="follow_set_type_description">Symbol für %1$s-Liste</string>
|
<string name="follow_set_icon_description">Symbol für %1$s-Liste</string>
|
||||||
<string name="follow_set_presence_indicator">"%1$s ist in dieser Liste"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"%1$s ist nicht in dieser Liste"</string>
|
<string name="follow_set_absence_indicator">"%1$s ist nicht in dieser Liste"</string>
|
||||||
<string name="follow_set_man_dialog_title">Deine Folge-Sets</string>
|
<string name="follow_set_man_dialog_title">Deine Folge-Sets</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Keine Folge-Sets gefunden oder du hast keine. Tippe unten zum Aktualisieren oder verwende das Menü, um eines zu erstellen.</string>
|
<string name="follow_set_empty_dialog_msg">Keine Folge-Sets gefunden oder du hast keine. Tippe unten zum Aktualisieren oder verwende das Menü, um eines zu erstellen.</string>
|
||||||
|
|||||||
@@ -1001,8 +1001,7 @@ anz der Bedingungen ist erforderlich</string>
|
|||||||
<string name="follow_set_empty_feed_msg">Es scheint, dass du noch keine Folge-Sets hast.\nTippe unten zum Aktualisieren oder verwende die Plus-Taste, um ein neues zu erstellen.</string>
|
<string name="follow_set_empty_feed_msg">Es scheint, dass du noch keine Folge-Sets hast.\nTippe unten zum Aktualisieren oder verwende die Plus-Taste, um ein neues zu erstellen.</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Autor zum Folge-Set hinzufügen</string>
|
<string name="follow_set_add_author_from_note_action">Autor zum Folge-Set hinzufügen</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Benutzer zu Listen hinzufügen oder entfernen, oder eine neue Liste mit diesem Benutzer erstellen.</string>
|
<string name="follow_set_profile_actions_menu_description">Benutzer zu Listen hinzufügen oder entfernen, oder eine neue Liste mit diesem Benutzer erstellen.</string>
|
||||||
<string name="follow_set_type_description">Symbol für %1$s-Liste</string>
|
<string name="follow_set_icon_description">Symbol für %1$s-Liste</string>
|
||||||
<string name="follow_set_presence_indicator">%1$s ist in dieser Liste</string>
|
|
||||||
<string name="follow_set_absence_indicator">%1$s ist nicht in dieser Liste</string>
|
<string name="follow_set_absence_indicator">%1$s ist nicht in dieser Liste</string>
|
||||||
<string name="follow_set_man_dialog_title">Deine Folge-Sets</string>
|
<string name="follow_set_man_dialog_title">Deine Folge-Sets</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Keine Folge-Sets gefunden oder du hast keine. Tippe unten zum Aktualisieren oder verwende das Menü, um eines zu erstellen.</string>
|
<string name="follow_set_empty_dialog_msg">Keine Folge-Sets gefunden oder du hast keine. Tippe unten zum Aktualisieren oder verwende das Menü, um eines zu erstellen.</string>
|
||||||
|
|||||||
@@ -457,8 +457,7 @@
|
|||||||
</string>
|
</string>
|
||||||
<string name="follow_set_add_author_from_note_action">लेखक जोडें अनुगम्य सूची में</string>
|
<string name="follow_set_add_author_from_note_action">लेखक जोडें अनुगम्य सूची में</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">प्रयोक्ता को सूचियों में जोडें अथवा हटाएँ अथवा इस प्रयोक्ता के साथ नई सूची बनाएँ।</string>
|
<string name="follow_set_profile_actions_menu_description">प्रयोक्ता को सूचियों में जोडें अथवा हटाएँ अथवा इस प्रयोक्ता के साथ नई सूची बनाएँ।</string>
|
||||||
<string name="follow_set_type_description">सूची %1$s के लिए चिह्न</string>
|
<string name="follow_set_icon_description">सूची %1$s के लिए चिह्न</string>
|
||||||
<string name="follow_set_presence_indicator">"%1$s इस सूची में है"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"%1$s इस सूची में नहीं है"</string>
|
<string name="follow_set_absence_indicator">"%1$s इस सूची में नहीं है"</string>
|
||||||
<string name="follow_set_man_dialog_title">आपके अनुगम्य सूचियाँ</string>
|
<string name="follow_set_man_dialog_title">आपके अनुगम्य सूचियाँ</string>
|
||||||
<string name="follow_set_empty_dialog_msg">कोई अनुगम्य सूचियाँ प्राप्त नहीं। अथवा आपका कोई अनुगम्य सूचियाँ हैं नहीं। नवीकरण के लिए नीचे दबाएँ अथवा विकल्पसूची द्वारा एक नया बनाएँ।</string>
|
<string name="follow_set_empty_dialog_msg">कोई अनुगम्य सूचियाँ प्राप्त नहीं। अथवा आपका कोई अनुगम्य सूचियाँ हैं नहीं। नवीकरण के लिए नीचे दबाएँ अथवा विकल्पसूची द्वारा एक नया बनाएँ।</string>
|
||||||
|
|||||||
@@ -457,8 +457,7 @@
|
|||||||
</string>
|
</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Szerző hozzáadása a követési gyűjteményhez</string>
|
<string name="follow_set_add_author_from_note_action">Szerző hozzáadása a követési gyűjteményhez</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Felhasználó hozzáadása vagy eltávolítása a listákból, vagy új lista létrehozása ezzel a felhasználóval.</string>
|
<string name="follow_set_profile_actions_menu_description">Felhasználó hozzáadása vagy eltávolítása a listákból, vagy új lista létrehozása ezzel a felhasználóval.</string>
|
||||||
<string name="follow_set_type_description">Ikon a(z) %1$s nevű listához</string>
|
<string name="follow_set_icon_description">Ikon a(z) %1$s nevű listához</string>
|
||||||
<string name="follow_set_presence_indicator">"A(z) %1$s már a létezik a listában"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"A(z) %1$s nincs a listában"</string>
|
<string name="follow_set_absence_indicator">"A(z) %1$s nincs a listában"</string>
|
||||||
<string name="follow_set_man_dialog_title">Saját követési gyüjtemények</string>
|
<string name="follow_set_man_dialog_title">Saját követési gyüjtemények</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Nem találhatók követési gyüjtemények, vagy nincs követési gyüjteménye. Érintse meg az alábbi gombot a frissítéshez vagy használja a menüt egy gyüjtemény létrehozásához.</string>
|
<string name="follow_set_empty_dialog_msg">Nem találhatók követési gyüjtemények, vagy nincs követési gyüjteménye. Érintse meg az alábbi gombot a frissítéshez vagy használja a menüt egy gyüjtemény létrehozásához.</string>
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
<string name="yes">Jā</string>
|
<string name="yes">Jā</string>
|
||||||
<string name="no">Nē</string>
|
<string name="no">Nē</string>
|
||||||
<string name="follow_list_selection">Sekot saraksts</string>
|
<string name="follow_list_selection">Sekot saraksts</string>
|
||||||
<string name="follow_set_type_description">Ikona %1$s sarakstam</string>
|
<string name="follow_set_icon_description">Ikona %1$s sarakstam</string>
|
||||||
<string name="follow_set_creation_menu_title">Izveidot jaunu sarakstu</string>
|
<string name="follow_set_creation_menu_title">Izveidot jaunu sarakstu</string>
|
||||||
<string name="follow_set_creation_dialog_title">Jaunais %1$s saraksts</string>
|
<string name="follow_set_creation_dialog_title">Jaunais %1$s saraksts</string>
|
||||||
<string name="follow_set_creation_name_label">Kolekcijas nosaukums</string>
|
<string name="follow_set_creation_name_label">Kolekcijas nosaukums</string>
|
||||||
|
|||||||
@@ -454,8 +454,7 @@
|
|||||||
</string>
|
</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Dodaj autora do zbioru obserwowanych</string>
|
<string name="follow_set_add_author_from_note_action">Dodaj autora do zbioru obserwowanych</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Dodaj lub usuń użytkownika z list, lub utwórz nową listę z tym użytkownikiem.</string>
|
<string name="follow_set_profile_actions_menu_description">Dodaj lub usuń użytkownika z list, lub utwórz nową listę z tym użytkownikiem.</string>
|
||||||
<string name="follow_set_type_description">Ikona dla listy %1$s</string>
|
<string name="follow_set_icon_description">Ikona dla listy %1$s</string>
|
||||||
<string name="follow_set_presence_indicator">"%1$s jest obecny na liście"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"%1$s nie jest na liście"</string>
|
<string name="follow_set_absence_indicator">"%1$s nie jest na liście"</string>
|
||||||
<string name="follow_set_man_dialog_title">Twój zbiór obserwowanych</string>
|
<string name="follow_set_man_dialog_title">Twój zbiór obserwowanych</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Nie znaleziono zbiorów obserwowanych lub nie masz żadnych zbiorów obserwowanych. Dotknij poniżej, aby odświeżyć lub użyj menu, aby go utworzyć.</string>
|
<string name="follow_set_empty_dialog_msg">Nie znaleziono zbiorów obserwowanych lub nie masz żadnych zbiorów obserwowanych. Dotknij poniżej, aby odświeżyć lub użyj menu, aby go utworzyć.</string>
|
||||||
|
|||||||
@@ -453,8 +453,7 @@
|
|||||||
<string name="follow_set_empty_feed_msg">Parece que você ainda não tem conjuntos de seguimento.\nToque abaixo para atualizar ou use o botão de adicionar para criar um novo.</string>
|
<string name="follow_set_empty_feed_msg">Parece que você ainda não tem conjuntos de seguimento.\nToque abaixo para atualizar ou use o botão de adicionar para criar um novo.</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Adicionar autor ao conjunto de seguimento</string>
|
<string name="follow_set_add_author_from_note_action">Adicionar autor ao conjunto de seguimento</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Adicionar ou remover usuário de listas, ou criar uma nova lista com este usuário.</string>
|
<string name="follow_set_profile_actions_menu_description">Adicionar ou remover usuário de listas, ou criar uma nova lista com este usuário.</string>
|
||||||
<string name="follow_set_type_description">Ícone da lista %1$s</string>
|
<string name="follow_set_icon_description">Ícone da lista %1$s</string>
|
||||||
<string name="follow_set_presence_indicator">"%1$s está presente nesta lista"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"%1$s não está nesta lista"</string>
|
<string name="follow_set_absence_indicator">"%1$s não está nesta lista"</string>
|
||||||
<string name="follow_set_man_dialog_title">Seus conjuntos de seguimento</string>
|
<string name="follow_set_man_dialog_title">Seus conjuntos de seguimento</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Nenhum conjunto de seguimento foi encontrado ou você não possui nenhum. Toque abaixo para atualizar ou use o menu para criar um.</string>
|
<string name="follow_set_empty_dialog_msg">Nenhum conjunto de seguimento foi encontrado ou você não possui nenhum. Toque abaixo para atualizar ou use o menu para criar um.</string>
|
||||||
|
|||||||
@@ -453,8 +453,7 @@
|
|||||||
<string name="follow_set_empty_feed_msg">Det verkar som att du inte har några följ-set ännu.\nTryck nedan för att uppdatera eller använd plusknappen för att skapa ett nytt.</string>
|
<string name="follow_set_empty_feed_msg">Det verkar som att du inte har några följ-set ännu.\nTryck nedan för att uppdatera eller använd plusknappen för att skapa ett nytt.</string>
|
||||||
<string name="follow_set_add_author_from_note_action">Lägg till författare i följ-set</string>
|
<string name="follow_set_add_author_from_note_action">Lägg till författare i följ-set</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">Lägg till eller ta bort användare från listor, eller skapa en ny lista med denna användare.</string>
|
<string name="follow_set_profile_actions_menu_description">Lägg till eller ta bort användare från listor, eller skapa en ny lista med denna användare.</string>
|
||||||
<string name="follow_set_type_description">Ikon för %1$s-lista</string>
|
<string name="follow_set_icon_description">Ikon för %1$s-lista</string>
|
||||||
<string name="follow_set_presence_indicator">"%1$s finns i denna lista"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"%1$s finns inte i denna lista"</string>
|
<string name="follow_set_absence_indicator">"%1$s finns inte i denna lista"</string>
|
||||||
<string name="follow_set_man_dialog_title">Dina följ-set</string>
|
<string name="follow_set_man_dialog_title">Dina följ-set</string>
|
||||||
<string name="follow_set_empty_dialog_msg">Inga följ-set hittades, eller så har du inga. Tryck nedan för att uppdatera eller använd menyn för att skapa ett.</string>
|
<string name="follow_set_empty_dialog_msg">Inga följ-set hittades, eller så har du inga. Tryck nedan för att uppdatera eller använd menyn för att skapa ett.</string>
|
||||||
|
|||||||
@@ -457,8 +457,7 @@
|
|||||||
</string>
|
</string>
|
||||||
<string name="follow_set_add_author_from_note_action">添加作者到关注集</string>
|
<string name="follow_set_add_author_from_note_action">添加作者到关注集</string>
|
||||||
<string name="follow_set_profile_actions_menu_description">从列表中添加或删除用户,或用此用户创建一个新列表。</string>
|
<string name="follow_set_profile_actions_menu_description">从列表中添加或删除用户,或用此用户创建一个新列表。</string>
|
||||||
<string name="follow_set_type_description">%1$s 列表的图标</string>
|
<string name="follow_set_icon_description">%1$s 列表的图标</string>
|
||||||
<string name="follow_set_presence_indicator">"此列表中有 %1$s"</string>
|
|
||||||
<string name="follow_set_absence_indicator">"此列表中没有 %1$s"</string>
|
<string name="follow_set_absence_indicator">"此列表中没有 %1$s"</string>
|
||||||
<string name="follow_set_man_dialog_title">您的关注集</string>
|
<string name="follow_set_man_dialog_title">您的关注集</string>
|
||||||
<string name="follow_set_empty_dialog_msg">未找到关注集,或者你还没有任何关注集。轻按下方刷新,或使用按钮新建。</string>
|
<string name="follow_set_empty_dialog_msg">未找到关注集,或者你还没有任何关注集。轻按下方刷新,或使用按钮新建。</string>
|
||||||
|
|||||||
@@ -519,22 +519,40 @@
|
|||||||
It seems you do not have any follow sets yet.
|
It seems you do not have any follow sets yet.
|
||||||
\nTap below to refresh, or tap the add buttons to create a new one.
|
\nTap below to refresh, or tap the add buttons to create a new one.
|
||||||
</string>
|
</string>
|
||||||
|
<string name="follow_set_create_btn_label">New</string>
|
||||||
<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_icon_description">Icon for follow set</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_public_member_add_label">Add as public member</string>
|
||||||
|
<string name="follow_set_private_member_add_label">Add as private member</string>
|
||||||
|
<string name="follow_set_public_members_header_label">Public Profiles</string>
|
||||||
|
<string name="follow_set_private_members_header_label">Private Profiles</string>
|
||||||
|
<string name="follow_set_single_member_label">member</string>
|
||||||
|
<string name="follow_set_multiple_member_label">members</string>
|
||||||
|
<string name="follow_set_empty_label">No 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_copy_dialog_title">Copy/Clone Follow Set</string>
|
||||||
|
<string name="follow_set_desc_modify_label">Modify description</string>
|
||||||
|
<string name="follow_set_empty_desc_label">This list doesn\'t have a description</string>
|
||||||
|
<string name="follow_set_current_desc_label">Current description:</string>
|
||||||
|
<string name="follow_set_copy_indicator_description">You can set a custom name/description for this clone set below.</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_copy_name_label">(Original 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_copy_desc_label">(Original Set description)</string>
|
||||||
<string name="follow_set_creation_action_btn_label">Create set</string>
|
<string name="follow_set_creation_action_btn_label">Create set</string>
|
||||||
|
<string name="follow_set_copy_action_btn_label">Copy/Clone set</string>
|
||||||
<string name="follow_set_rename_btn_label">Rename set</string>
|
<string name="follow_set_rename_btn_label">Rename set</string>
|
||||||
|
<string name="follow_set_desc_modify_btn_label">Modify</string>
|
||||||
<string name="follow_set_rename_dialog_indicator_first_part">You are renaming from </string>
|
<string name="follow_set_rename_dialog_indicator_first_part">You are renaming from </string>
|
||||||
<string name="follow_set_rename_dialog_indicator_second_part"> to..</string>
|
<string name="follow_set_rename_dialog_indicator_second_part"> to..</string>
|
||||||
|
|
||||||
|
|||||||
+123
-40
@@ -155,6 +155,31 @@ class PeopleListEvent(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun remove(
|
||||||
|
earlierVersion: PeopleListEvent,
|
||||||
|
person: UserTag,
|
||||||
|
isPrivate: Boolean,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
): PeopleListEvent {
|
||||||
|
if (isPrivate) {
|
||||||
|
val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||||
|
return resign(
|
||||||
|
publicTags = earlierVersion.tags,
|
||||||
|
privateTags = privateTags.remove(person.toTagArray()),
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return resign(
|
||||||
|
content = earlierVersion.content,
|
||||||
|
tags = earlierVersion.tags.remove(person.toTagArray()),
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun resign(
|
suspend fun resign(
|
||||||
publicTags: TagArray,
|
publicTags: TagArray,
|
||||||
privateTags: TagArray,
|
privateTags: TagArray,
|
||||||
@@ -223,51 +248,61 @@ class PeopleListEvent(
|
|||||||
title: String,
|
title: String,
|
||||||
description: String? = null,
|
description: String? = null,
|
||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
firstMemberHex: String? = null,
|
firstPublicMembers: List<String> = emptyList(),
|
||||||
|
firstPrivateMembers: List<String> = emptyList(),
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
onReady: (PeopleListEvent) -> Unit,
|
onReady: (PeopleListEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (description == null) {
|
val newListTemplate =
|
||||||
val newList =
|
build(
|
||||||
create(
|
name = title,
|
||||||
name = title,
|
publicPeople =
|
||||||
person = UserTag(pubKey = firstMemberHex ?: signer.pubKey),
|
if (!isPrivate && firstPublicMembers.isNotEmpty()) {
|
||||||
isPrivate = isPrivate,
|
firstPublicMembers.map { UserTag(pubKey = it) }
|
||||||
signer = signer,
|
} else {
|
||||||
dTag = dTag,
|
emptyList()
|
||||||
createdAt = createdAt,
|
},
|
||||||
)
|
privatePeople =
|
||||||
onReady(newList)
|
if (isPrivate && firstPrivateMembers.isNotEmpty()) {
|
||||||
} else {
|
firstPrivateMembers.map { UserTag(pubKey = it) }
|
||||||
if (isPrivate) {
|
} else {
|
||||||
val event =
|
emptyList()
|
||||||
build(
|
},
|
||||||
name = title,
|
signer = signer,
|
||||||
privatePeople = listOf(UserTag(pubKey = firstMemberHex ?: signer.pubKey)),
|
dTag = dTag,
|
||||||
signer = signer,
|
createdAt = createdAt,
|
||||||
dTag = dTag,
|
) {
|
||||||
createdAt = createdAt,
|
if (description != null) addUnique(DescriptionTag.assemble(description))
|
||||||
) {
|
|
||||||
addUnique(arrayOf("description", description))
|
|
||||||
}
|
|
||||||
val list = signer.sign(event)
|
|
||||||
onReady(list)
|
|
||||||
} else {
|
|
||||||
val event =
|
|
||||||
build(
|
|
||||||
name = title,
|
|
||||||
publicPeople = listOf(UserTag(pubKey = firstMemberHex ?: signer.pubKey)),
|
|
||||||
signer = signer,
|
|
||||||
dTag = dTag,
|
|
||||||
createdAt = createdAt,
|
|
||||||
) {
|
|
||||||
addUnique(arrayOf("description", description))
|
|
||||||
}
|
|
||||||
val list = signer.sign(event)
|
|
||||||
onReady(list)
|
|
||||||
}
|
}
|
||||||
}
|
val newList = signer.sign(newListTemplate)
|
||||||
|
onReady(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun copy(
|
||||||
|
dTag: String,
|
||||||
|
title: String,
|
||||||
|
description: String? = null,
|
||||||
|
firstPublicMembers: List<String> = emptyList(),
|
||||||
|
firstPrivateMembers: List<String> = emptyList(),
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (PeopleListEvent) -> Unit,
|
||||||
|
) {
|
||||||
|
val cloneTemplate =
|
||||||
|
build(
|
||||||
|
name = title,
|
||||||
|
publicPeople = firstPublicMembers.map { UserTag(pubKey = it) },
|
||||||
|
privatePeople = firstPrivateMembers.map { UserTag(pubKey = it) },
|
||||||
|
signer = signer,
|
||||||
|
dTag = dTag,
|
||||||
|
createdAt = createdAt,
|
||||||
|
) {
|
||||||
|
if (description != null) addUnique(DescriptionTag.assemble(description))
|
||||||
|
}
|
||||||
|
|
||||||
|
val listClone = signer.sign(cloneTemplate)
|
||||||
|
onReady(listClone)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createListWithUser(
|
suspend fun createListWithUser(
|
||||||
@@ -311,6 +346,7 @@ class PeopleListEvent(
|
|||||||
suspend fun removeUser(
|
suspend fun removeUser(
|
||||||
earlierVersion: PeopleListEvent,
|
earlierVersion: PeopleListEvent,
|
||||||
pubKeyHex: String,
|
pubKeyHex: String,
|
||||||
|
isUserPrivate: Boolean,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
onReady: (PeopleListEvent) -> Unit,
|
onReady: (PeopleListEvent) -> Unit,
|
||||||
@@ -319,6 +355,7 @@ class PeopleListEvent(
|
|||||||
remove(
|
remove(
|
||||||
earlierVersion = earlierVersion,
|
earlierVersion = earlierVersion,
|
||||||
person = UserTag(pubKey = pubKeyHex),
|
person = UserTag(pubKey = pubKeyHex),
|
||||||
|
isPrivate = isUserPrivate,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
createdAt = createdAt,
|
createdAt = createdAt,
|
||||||
)
|
)
|
||||||
@@ -351,5 +388,51 @@ class PeopleListEvent(
|
|||||||
)
|
)
|
||||||
onReady(modified)
|
onReady(modified)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun modifyDescription(
|
||||||
|
earlierVersion: PeopleListEvent,
|
||||||
|
newDescription: String?,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
onReady: (PeopleListEvent) -> Unit = {},
|
||||||
|
) {
|
||||||
|
val privateTags = earlierVersion.privateTags(signer) ?: throw SignerExceptions.UnauthorizedDecryptionException()
|
||||||
|
val currentDescriptionTag = earlierVersion.tags.firstOrNull { it[0] == DescriptionTag.TAG_NAME }
|
||||||
|
val currentDescription = currentDescriptionTag?.get(1)
|
||||||
|
if (currentDescription.equals(newDescription)) {
|
||||||
|
// Do nothing
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
if (newDescription == null || newDescription.isEmpty()) {
|
||||||
|
val modified =
|
||||||
|
resign(
|
||||||
|
publicTags = earlierVersion.tags.remove { it[0] == DescriptionTag.TAG_NAME },
|
||||||
|
privateTags = privateTags.remove { it[0] == DescriptionTag.TAG_NAME },
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
onReady(modified)
|
||||||
|
} else {
|
||||||
|
val newDescriptionTag = DescriptionTag.assemble(newDescription)
|
||||||
|
val modified =
|
||||||
|
if (currentDescriptionTag == null) {
|
||||||
|
resign(
|
||||||
|
publicTags = earlierVersion.tags.plusElement(newDescriptionTag),
|
||||||
|
privateTags = privateTags,
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
resign(
|
||||||
|
publicTags = earlierVersion.tags.replaceAll(currentDescriptionTag, newDescriptionTag),
|
||||||
|
privateTags = privateTags.replaceAll(currentDescriptionTag, newDescriptionTag),
|
||||||
|
signer = signer,
|
||||||
|
createdAt = createdAt,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onReady(modified)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user