mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 13:17:34 +01:00
Merge branch 'main' into bugfix-dont-render-gallery-for-mixed-images-and-videos
This commit is contained in:
@@ -151,6 +151,7 @@ android {
|
||||
signingConfig = signingConfigs.debug
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this when lightcompressor uses one MP4 parser only
|
||||
packaging {
|
||||
resources {
|
||||
|
||||
@@ -31,9 +31,10 @@ data class FollowSet(
|
||||
val identifierTag: String,
|
||||
val title: String,
|
||||
val description: String?,
|
||||
val visibility: SetVisibility,
|
||||
val profiles: Set<String>,
|
||||
) : NostrSet(setVisibility = visibility, content = profiles) {
|
||||
val visibility: SetVisibility = SetVisibility.Mixed,
|
||||
val privateProfiles: Set<String> = emptySet(),
|
||||
val publicProfiles: Set<String> = emptySet(),
|
||||
) : NostrSet(setVisibility = visibility, privateContent = privateProfiles, publicContent = publicProfiles) {
|
||||
companion object {
|
||||
fun mapEventToSet(
|
||||
event: PeopleListEvent,
|
||||
@@ -53,26 +54,22 @@ data class FollowSet(
|
||||
identifierTag = dTag,
|
||||
title = listTitle,
|
||||
description = listDescription,
|
||||
visibility = SetVisibility.Private,
|
||||
profiles = privateFollows.toSet(),
|
||||
privateProfiles = privateFollows.toSet(),
|
||||
)
|
||||
} else if (publicFollows.isNotEmpty() && privateFollows.isEmpty()) {
|
||||
FollowSet(
|
||||
identifierTag = dTag,
|
||||
title = listTitle,
|
||||
description = listDescription,
|
||||
visibility = SetVisibility.Public,
|
||||
profiles = publicFollows.toSet(),
|
||||
publicProfiles = publicFollows.toSet(),
|
||||
)
|
||||
} else {
|
||||
// Follow set is empty, so assume public. Why? Nostr limitation.
|
||||
// TODO: Could this be fixed at protocol level?
|
||||
FollowSet(
|
||||
identifierTag = dTag,
|
||||
title = listTitle,
|
||||
description = listDescription,
|
||||
visibility = SetVisibility.Public,
|
||||
profiles = publicFollows.toSet(),
|
||||
privateProfiles = privateFollows.toSet(),
|
||||
publicProfiles = publicFollows.toSet(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,20 +25,16 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class FollowSetState(
|
||||
@@ -50,7 +46,7 @@ class FollowSetState(
|
||||
private val isActive = MutableStateFlow(false)
|
||||
|
||||
suspend fun getFollowSetNotes() =
|
||||
withContext(Dispatchers.Default) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val followSetNotes = LocalCache.getFollowSetNotesFor(user)
|
||||
return@withContext followSetNotes
|
||||
}
|
||||
@@ -63,12 +59,12 @@ class FollowSetState(
|
||||
emit(followSets)
|
||||
delay(2000)
|
||||
}
|
||||
}.flowOn(Dispatchers.Default)
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
val profilesFlow =
|
||||
getFollowSetNotesFlow()
|
||||
.map { it ->
|
||||
it.flatMapTo(mutableSetOf()) { it.profiles }.toSet()
|
||||
it.flatMapTo(mutableSetOf()) { it.privateProfiles + it.publicProfiles }.toSet()
|
||||
}.stateIn(scope, SharingStarted.Eagerly, emptySet())
|
||||
|
||||
fun mapNoteToFollowSet(note: Note): FollowSet =
|
||||
@@ -82,14 +78,5 @@ class FollowSetState(
|
||||
|
||||
init {
|
||||
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 {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@ package com.vitorpamplona.amethyst.model.nip51Lists.followSets
|
||||
|
||||
sealed class NostrSet(
|
||||
val setVisibility: SetVisibility,
|
||||
val content: Collection<String>,
|
||||
val privateContent: Collection<String>,
|
||||
val publicContent: Collection<String>,
|
||||
)
|
||||
|
||||
class CuratedBookmarkSet(
|
||||
val name: String,
|
||||
val visibility: SetVisibility,
|
||||
val setItems: List<String>,
|
||||
) : NostrSet(visibility, setItems)
|
||||
val privateSetItems: List<String>,
|
||||
val publicSetItems: List<String>,
|
||||
) : NostrSet(visibility, privateSetItems, publicSetItems)
|
||||
|
||||
@@ -257,16 +257,9 @@ object VideoCompressionHelper {
|
||||
"Compressed [$size] ($reductionPercent% reduction)",
|
||||
)
|
||||
|
||||
// Attempt to correct the path: if it contains "_temp" then remove it
|
||||
val correctedPath =
|
||||
if (path.contains("_temp")) {
|
||||
path.replace("_temp", "")
|
||||
} else {
|
||||
path
|
||||
}
|
||||
if (continuation.isActive) {
|
||||
continuation.resume(
|
||||
MediaCompressorResult(Uri.fromFile(File(correctedPath)), contentType, size),
|
||||
MediaCompressorResult(Uri.fromFile(File(path)), contentType, size),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,11 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
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.Button
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Tab
|
||||
@@ -48,8 +50,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -119,12 +119,11 @@ fun ListsAndSetsScreen(
|
||||
refresh = {
|
||||
followSetsViewModel.invalidateData()
|
||||
},
|
||||
addItem = { title: String, description: String?, listType: SetVisibility ->
|
||||
val isSetPrivate = listType == SetVisibility.Private
|
||||
addItem = { title: String, description: String? ->
|
||||
|
||||
followSetsViewModel.addFollowSet(
|
||||
setName = title,
|
||||
setDescription = description,
|
||||
isListPrivate = isSetPrivate,
|
||||
account = accountViewModel.account,
|
||||
)
|
||||
},
|
||||
@@ -138,6 +137,21 @@ fun ListsAndSetsScreen(
|
||||
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 ->
|
||||
followSetsViewModel.deleteFollowSet(
|
||||
followSet = followSet,
|
||||
@@ -153,14 +167,16 @@ fun ListsAndSetsScreen(
|
||||
fun CustomListsScreen(
|
||||
followSetFeedState: FollowSetFeedState,
|
||||
refresh: () -> Unit,
|
||||
addItem: (title: String, description: String?, listType: SetVisibility) -> Unit,
|
||||
addItem: (title: String, description: String?) -> Unit,
|
||||
openItem: (identifier: 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,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val pagerState = rememberPagerState { 3 }
|
||||
val pagerState = rememberPagerState { 2 }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
DisappearingScaffold(
|
||||
@@ -185,22 +201,14 @@ fun CustomListsScreen(
|
||||
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
|
||||
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 = {
|
||||
// TODO: Show components based on current tab
|
||||
FollowSetFabsAndMenu(
|
||||
onAddPrivateSet = { name: String, description: String? ->
|
||||
addItem(name, description, SetVisibility.Private)
|
||||
},
|
||||
onAddPublicSet = { name: String, description: String? ->
|
||||
addItem(name, description, SetVisibility.Public)
|
||||
onAddSet = { name: String, description: String? ->
|
||||
addItem(name, description)
|
||||
},
|
||||
)
|
||||
},
|
||||
@@ -222,11 +230,13 @@ fun CustomListsScreen(
|
||||
onRefresh = refresh,
|
||||
onOpenItem = openItem,
|
||||
onRenameItem = renameItem,
|
||||
onItemDescriptionChange = changeItemDescription,
|
||||
onItemClone = cloneItem,
|
||||
onDeleteItem = deleteItem,
|
||||
)
|
||||
|
||||
1 -> LabeledBookmarksFeedView()
|
||||
2 -> GeneralBookmarksFeedView()
|
||||
// 2 -> GeneralBookmarksFeedView()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -236,57 +246,34 @@ fun CustomListsScreen(
|
||||
@Composable
|
||||
private fun FollowSetFabsAndMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
onAddPrivateSet: (name: String, description: String?) -> Unit,
|
||||
onAddPublicSet: (name: String, description: String?) -> Unit,
|
||||
onAddSet: (name: String, description: String?) -> Unit,
|
||||
) {
|
||||
val isSetAdditionDialogOpen = remember { mutableStateOf(false) }
|
||||
val isPrivateOptionTapped = remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
isPrivateOptionTapped.value = true
|
||||
isSetAdditionDialogOpen.value = true
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
Text(text = stringRes(R.string.follow_set_create_btn_label))
|
||||
},
|
||||
shape = CircleShape,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.lock_plus),
|
||||
imageVector = Icons.AutoMirrored.Filled.PlaylistAdd,
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
FloatingActionButton(
|
||||
},
|
||||
onClick = {
|
||||
isSetAdditionDialogOpen.value = true
|
||||
},
|
||||
shape = CircleShape,
|
||||
containerColor = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.earth_plus),
|
||||
contentDescription = null,
|
||||
tint = Color.White,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isSetAdditionDialogOpen.value) {
|
||||
NewSetCreationDialog(
|
||||
onDismiss = {
|
||||
isSetAdditionDialogOpen.value = false
|
||||
isPrivateOptionTapped.value = false
|
||||
},
|
||||
shouldBePrivate = isPrivateOptionTapped.value,
|
||||
onCreateList = { name, description ->
|
||||
if (isPrivateOptionTapped.value) {
|
||||
onAddPrivateSet(name, description)
|
||||
} else {
|
||||
onAddPublicSet(name, description)
|
||||
}
|
||||
onAddSet(name, description)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -296,27 +283,10 @@ private fun FollowSetFabsAndMenu(
|
||||
fun NewSetCreationDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
onDismiss: () -> Unit,
|
||||
shouldBePrivate: Boolean,
|
||||
onCreateList: (name: String, description: String?) -> Unit,
|
||||
) {
|
||||
val newListName = remember { mutableStateOf("") }
|
||||
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(
|
||||
onDismissRequest = onDismiss,
|
||||
@@ -325,12 +295,8 @@ fun NewSetCreationDialog(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(listTypeIcon),
|
||||
contentDescription = null,
|
||||
)
|
||||
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)
|
||||
// For the list description
|
||||
// For the set description
|
||||
TextField(
|
||||
value =
|
||||
(
|
||||
@@ -425,6 +391,12 @@ private fun SetItemPreview() {
|
||||
onFollowSetRename = {
|
||||
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 = {
|
||||
println(" The follow set ${sampleFollowSet.title} has been deleted.")
|
||||
},
|
||||
|
||||
@@ -37,13 +37,10 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
@@ -56,15 +53,14 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
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.note.VerticalDotsIcon
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
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.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import kotlin.let
|
||||
|
||||
@Composable
|
||||
fun CustomSetItem(
|
||||
@@ -72,6 +68,8 @@ fun CustomSetItem(
|
||||
followSet: FollowSet,
|
||||
onFollowSetClick: () -> Unit,
|
||||
onFollowSetRename: (String) -> Unit,
|
||||
onFollowSetDescriptionChange: (String?) -> Unit,
|
||||
onFollowSetClone: (customName: String?, customDescription: String?) -> Unit,
|
||||
onFollowSetDelete: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -96,11 +94,12 @@ fun CustomSetItem(
|
||||
) {
|
||||
Text(followSet.title, fontWeight = FontWeight.Bold)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
if (followSet.publicProfiles.isEmpty() && followSet.privateProfiles.isEmpty()) {
|
||||
FilterChip(
|
||||
selected = true,
|
||||
onClick = {},
|
||||
label = {
|
||||
Text(text = "${followSet.profiles.size}")
|
||||
Text(text = stringRes(R.string.follow_set_empty_label))
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
@@ -111,6 +110,60 @@ fun CustomSetItem(
|
||||
shape = ButtonBorder,
|
||||
)
|
||||
}
|
||||
if (followSet.publicProfiles.isNotEmpty()) {
|
||||
val publicMemberSize = followSet.publicProfiles.size
|
||||
val membersLabel =
|
||||
stringRes(
|
||||
context,
|
||||
if (publicMemberSize == 1) {
|
||||
R.string.follow_set_single_member_label
|
||||
} else {
|
||||
R.string.follow_set_multiple_member_label
|
||||
},
|
||||
)
|
||||
FilterChip(
|
||||
selected = true,
|
||||
onClick = {},
|
||||
label = {
|
||||
Text(text = "$publicMemberSize")
|
||||
},
|
||||
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)
|
||||
Text(
|
||||
followSet.description ?: "",
|
||||
@@ -119,34 +172,6 @@ fun CustomSetItem(
|
||||
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(
|
||||
@@ -159,7 +184,10 @@ fun CustomSetItem(
|
||||
) {
|
||||
SetOptionsButton(
|
||||
followSetName = followSet.title,
|
||||
onListRename = onFollowSetRename,
|
||||
followSetDescription = followSet.description,
|
||||
onSetRename = onFollowSetRename,
|
||||
onSetDescriptionChange = onFollowSetDescriptionChange,
|
||||
onSetCloneCreate = onFollowSetClone,
|
||||
onListDelete = onFollowSetDelete,
|
||||
)
|
||||
}
|
||||
@@ -167,10 +195,13 @@ fun CustomSetItem(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SetOptionsButton(
|
||||
private fun SetOptionsButton(
|
||||
modifier: Modifier = Modifier,
|
||||
followSetName: String,
|
||||
onListRename: (String) -> Unit,
|
||||
followSetDescription: String?,
|
||||
onSetRename: (String) -> Unit,
|
||||
onSetDescriptionChange: (String?) -> Unit,
|
||||
onSetCloneCreate: (optionalName: String?, optionalDec: String?) -> Unit,
|
||||
onListDelete: () -> Unit,
|
||||
) {
|
||||
val isMenuOpen = remember { mutableStateOf(false) }
|
||||
@@ -181,10 +212,13 @@ fun SetOptionsButton(
|
||||
VerticalDotsIcon()
|
||||
|
||||
SetOptionsMenu(
|
||||
listName = followSetName,
|
||||
setName = followSetName,
|
||||
setDescription = followSetDescription,
|
||||
isExpanded = isMenuOpen.value,
|
||||
onDismiss = { isMenuOpen.value = false },
|
||||
onListRename = onListRename,
|
||||
onSetRename = onSetRename,
|
||||
onSetDescriptionChange = onSetDescriptionChange,
|
||||
onSetClone = onSetCloneCreate,
|
||||
onDelete = onListDelete,
|
||||
)
|
||||
}
|
||||
@@ -194,26 +228,27 @@ fun SetOptionsButton(
|
||||
private fun SetOptionsMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
isExpanded: Boolean,
|
||||
listName: String,
|
||||
onListRename: (String) -> Unit,
|
||||
setName: String,
|
||||
setDescription: String?,
|
||||
onSetRename: (String) -> Unit,
|
||||
onSetDescriptionChange: (String?) -> Unit,
|
||||
onSetClone: (optionalNewName: String?, optionalNewDesc: String?) -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val isRenameDialogOpen = remember { mutableStateOf(false) }
|
||||
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(
|
||||
expanded = isExpanded,
|
||||
onDismissRequest = onDismiss,
|
||||
) {
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = stringRes(R.string.quick_action_delete))
|
||||
},
|
||||
onClick = {
|
||||
onDelete()
|
||||
},
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = {
|
||||
Text(text = stringRes(R.string.follow_set_rename_btn_label))
|
||||
@@ -223,25 +258,76 @@ private fun SetOptionsMenu(
|
||||
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) {
|
||||
RenameDialog(
|
||||
currentName = listName,
|
||||
SetRenameDialog(
|
||||
currentName = setName,
|
||||
newName = renameString.value,
|
||||
onStringRenameChange = {
|
||||
renameString.value = it
|
||||
},
|
||||
onDismissDialog = { isRenameDialogOpen.value = false },
|
||||
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
|
||||
private fun RenameDialog(
|
||||
private fun SetRenameDialog(
|
||||
modifier: Modifier = Modifier,
|
||||
currentName: 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))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ fun FollowSetFeedView(
|
||||
onRefresh: () -> Unit = {},
|
||||
onOpenItem: (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,
|
||||
) {
|
||||
when (followSetFeedState) {
|
||||
@@ -63,6 +65,8 @@ fun FollowSetFeedView(
|
||||
onRefresh = onRefresh,
|
||||
onItemClick = onOpenItem,
|
||||
onItemRename = onRenameItem,
|
||||
onItemDescriptionChange = onItemDescriptionChange,
|
||||
onItemClone = onItemClone,
|
||||
onItemDelete = onDeleteItem,
|
||||
)
|
||||
}
|
||||
@@ -91,6 +95,8 @@ fun FollowSetLoaded(
|
||||
onRefresh: () -> Unit = {},
|
||||
onItemClick: (itemIdentifier: 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,
|
||||
) {
|
||||
Log.d("FollowSetComposable", "FollowSetLoaded: Follow Set size: ${loadedFeedState.size}")
|
||||
@@ -113,6 +119,12 @@ fun FollowSetLoaded(
|
||||
onFollowSetRename = {
|
||||
onItemRename(set, it)
|
||||
},
|
||||
onFollowSetDescriptionChange = { newDescription ->
|
||||
onItemDescriptionChange(set, newDescription)
|
||||
},
|
||||
onFollowSetClone = { cloneName, cloneDescription ->
|
||||
onItemClone(set, cloneName, cloneDescription)
|
||||
},
|
||||
onFollowSetDelete = {
|
||||
onItemDelete(set)
|
||||
},
|
||||
|
||||
@@ -31,7 +31,6 @@ import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
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.ui.dal.FeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.FollowSetFeedFilter
|
||||
@@ -117,8 +116,8 @@ class FollowSetFeedViewModel(
|
||||
fun addFollowSet(
|
||||
setName: String,
|
||||
setDescription: String?,
|
||||
isListPrivate: Boolean,
|
||||
optionalFirstMemberHex: String? = null,
|
||||
firstMemberShouldBePrivate: Boolean = false,
|
||||
account: Account,
|
||||
) {
|
||||
if (!account.settings.isWriteable()) {
|
||||
@@ -129,8 +128,9 @@ class FollowSetFeedViewModel(
|
||||
dTag = UUID.randomUUID().toString(),
|
||||
title = setName,
|
||||
description = setDescription,
|
||||
isPrivate = isListPrivate,
|
||||
firstMemberHex = optionalFirstMemberHex,
|
||||
isPrivate = firstMemberShouldBePrivate,
|
||||
firstPublicMembers = if (optionalFirstMemberHex != null) listOf(optionalFirstMemberHex) else emptyList(),
|
||||
firstPrivateMembers = if (optionalFirstMemberHex != null) listOf(optionalFirstMemberHex) else emptyList(),
|
||||
signer = account.signer,
|
||||
) {
|
||||
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(
|
||||
followSet: FollowSet,
|
||||
account: Account,
|
||||
@@ -179,6 +224,7 @@ class FollowSetFeedViewModel(
|
||||
fun addUserToSet(
|
||||
userProfileHex: String,
|
||||
followSet: FollowSet,
|
||||
shouldBePrivateMember: Boolean,
|
||||
account: Account,
|
||||
) {
|
||||
if (!account.settings.isWriteable()) {
|
||||
@@ -190,7 +236,7 @@ class FollowSetFeedViewModel(
|
||||
PeopleListEvent.addUser(
|
||||
earlierVersion = followSetEvent,
|
||||
pubKeyHex = userProfileHex,
|
||||
isPrivate = followSet.visibility == SetVisibility.Private,
|
||||
isPrivate = shouldBePrivateMember,
|
||||
signer = account.signer,
|
||||
) {
|
||||
account.sendMyPublicAndPrivateOutbox(it)
|
||||
@@ -201,6 +247,7 @@ class FollowSetFeedViewModel(
|
||||
|
||||
fun removeUserFromSet(
|
||||
userProfileHex: String,
|
||||
userIsPrivate: Boolean,
|
||||
followSet: FollowSet,
|
||||
account: Account,
|
||||
) {
|
||||
@@ -213,6 +260,7 @@ class FollowSetFeedViewModel(
|
||||
PeopleListEvent.removeUser(
|
||||
earlierVersion = followSetEvent,
|
||||
pubKeyHex = userProfileHex,
|
||||
isUserPrivate = userIsPrivate,
|
||||
signer = account.signer,
|
||||
) {
|
||||
account.sendMyPublicAndPrivateOutbox(it)
|
||||
|
||||
@@ -28,6 +28,8 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
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.padding
|
||||
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.model.User
|
||||
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.navigation.navs.INav
|
||||
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.lists.FollowSetFeedViewModel
|
||||
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.FeedPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.VertPadding
|
||||
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -144,7 +147,8 @@ fun FollowSetScreen(
|
||||
when {
|
||||
selectedSetState.value != null -> {
|
||||
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(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -171,6 +175,7 @@ fun FollowSetScreen(
|
||||
selectedSet,
|
||||
accountViewModel.account,
|
||||
)
|
||||
navigator.popBack()
|
||||
},
|
||||
)
|
||||
},
|
||||
@@ -190,10 +195,12 @@ fun FollowSetScreen(
|
||||
bottom = padding.calculateBottomPadding(),
|
||||
).consumeWindowInsets(padding)
|
||||
.imePadding(),
|
||||
followSetList = users,
|
||||
publicMemberList = publicMembers,
|
||||
privateMemberList = privateMembers,
|
||||
onDeleteUser = {
|
||||
followSetViewModel.removeUserFromSet(
|
||||
it,
|
||||
userIsPrivate = selectedSet.privateProfiles.contains(it),
|
||||
selectedSet,
|
||||
accountViewModel.account,
|
||||
)
|
||||
@@ -233,14 +240,7 @@ fun TitleAndDescription(
|
||||
)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
Icon(
|
||||
painter =
|
||||
painterResource(
|
||||
when (followSet.setVisibility) {
|
||||
SetVisibility.Public -> R.drawable.ic_public
|
||||
SetVisibility.Private -> R.drawable.lock
|
||||
SetVisibility.Mixed -> R.drawable.format_list_bulleted_type
|
||||
},
|
||||
),
|
||||
painter = painterResource(R.drawable.format_list_bulleted_type),
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
@@ -260,7 +260,8 @@ fun TitleAndDescription(
|
||||
@Composable
|
||||
private fun FollowSetListView(
|
||||
modifier: Modifier = Modifier,
|
||||
followSetList: List<User>,
|
||||
publicMemberList: List<User>,
|
||||
privateMemberList: List<User>,
|
||||
onDeleteUser: (String) -> Unit,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
@@ -272,7 +273,24 @@ private fun FollowSetListView(
|
||||
contentPadding = FeedPadding,
|
||||
state = listState,
|
||||
) {
|
||||
itemsIndexed(followSetList, key = { _, item -> item.pubkeyHex }) { _, item ->
|
||||
if (publicMemberList.isNotEmpty()) {
|
||||
stickyHeader {
|
||||
Column(
|
||||
modifier = VertPadding,
|
||||
) {
|
||||
Text(
|
||||
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,
|
||||
@@ -281,6 +299,36 @@ private fun FollowSetListView(
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.AlertDialogDefaults
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
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.semantics.Role
|
||||
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.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
@@ -165,16 +168,18 @@ fun FollowSetsManagementDialog(
|
||||
) {
|
||||
when (followSetsState) {
|
||||
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)
|
||||
FollowSetItem(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
listHeader = list.title,
|
||||
setVisibility = list.visibility,
|
||||
listHeader = set.title,
|
||||
setVisibility = set.visibility,
|
||||
userName = userInfo.toBestDisplayName(),
|
||||
isUserInList = list.profiles.contains(userHex),
|
||||
userIsPrivateMember = set.privateProfiles.contains(userHex),
|
||||
userIsPublicMember = set.publicProfiles.contains(userHex),
|
||||
onRemoveUser = {
|
||||
Log.d(
|
||||
"Amethyst",
|
||||
@@ -182,23 +187,31 @@ fun FollowSetsManagementDialog(
|
||||
)
|
||||
followSetsViewModel.removeUserFromSet(
|
||||
userHex,
|
||||
list,
|
||||
userIsPrivate = set.privateProfiles.contains(userHex),
|
||||
set,
|
||||
account,
|
||||
)
|
||||
Log.d(
|
||||
"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(
|
||||
"Amethyst",
|
||||
"ProfileActions: Adding item to list ...",
|
||||
)
|
||||
followSetsViewModel.addUserToSet(userHex, list, account)
|
||||
followSetsViewModel.addUserToSet(
|
||||
userHex,
|
||||
set,
|
||||
userShouldBePrivate,
|
||||
account,
|
||||
)
|
||||
Log.d(
|
||||
"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) {
|
||||
FollowSetsCreationMenu(
|
||||
userName = userInfo.toBestDisplayName(),
|
||||
onSetCreate = { setName, setIsPrivate, description ->
|
||||
onSetCreate = { setName, memberShouldBePrivate, description ->
|
||||
followSetsViewModel.addFollowSet(
|
||||
setName = setName,
|
||||
setDescription = description,
|
||||
isListPrivate = setIsPrivate,
|
||||
firstMemberShouldBePrivate = memberShouldBePrivate,
|
||||
optionalFirstMemberHex = userHex,
|
||||
account = account,
|
||||
)
|
||||
@@ -306,11 +319,13 @@ fun FollowSetItem(
|
||||
listHeader: String,
|
||||
setVisibility: SetVisibility,
|
||||
userName: String,
|
||||
isUserInList: Boolean,
|
||||
onAddUser: () -> Unit,
|
||||
userIsPrivateMember: Boolean,
|
||||
userIsPublicMember: Boolean,
|
||||
onAddUserToList: (shouldBePrivateMember: Boolean) -> Unit,
|
||||
onRemoveUser: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val isUserInList = userIsPrivateMember || userIsPublicMember
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
@@ -330,27 +345,11 @@ fun FollowSetItem(
|
||||
) {
|
||||
Text(listHeader, fontWeight = FontWeight.Bold)
|
||||
Spacer(modifier = StdHorzSpacer)
|
||||
setVisibility.let {
|
||||
val text by derivedStateOf {
|
||||
when (it) {
|
||||
SetVisibility.Public -> stringRes(context, R.string.follow_set_type_public)
|
||||
SetVisibility.Private -> stringRes(context, R.string.follow_set_type_private)
|
||||
SetVisibility.Mixed -> stringRes(context, R.string.follow_set_type_mixed)
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
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),
|
||||
painter = painterResource(R.drawable.format_list_bulleted_type),
|
||||
contentDescription = stringRes(R.string.follow_set_icon_description),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
Row {
|
||||
@@ -362,7 +361,11 @@ fun FollowSetItem(
|
||||
Text(
|
||||
text =
|
||||
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 {
|
||||
stringRes(R.string.follow_set_absence_indicator, userName)
|
||||
},
|
||||
@@ -389,9 +392,14 @@ fun FollowSetItem(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
val isUserAddTapped = remember { mutableStateOf(false) }
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (isUserInList) onRemoveUser() else onAddUser()
|
||||
if (isUserInList) {
|
||||
onRemoveUser()
|
||||
} else {
|
||||
isUserAddTapped.value = true
|
||||
}
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
@@ -423,15 +431,55 @@ fun FollowSetItem(
|
||||
text = stringRes(if (isUserInList) R.string.remove else R.string.add),
|
||||
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
|
||||
fun FollowSetsCreationMenu(
|
||||
modifier: Modifier = Modifier,
|
||||
userName: String,
|
||||
onSetCreate: (setName: String, setIsPrivate: Boolean, description: String?) -> Unit,
|
||||
onSetCreate: (setName: String, memberShouldBePrivate: Boolean, description: String?) -> Unit,
|
||||
) {
|
||||
val isListAdditionDialogOpen = remember { mutableStateOf(false) }
|
||||
val isPrivateOptionTapped = remember { mutableStateOf(false) }
|
||||
@@ -474,7 +522,6 @@ fun FollowSetsCreationMenu(
|
||||
isListAdditionDialogOpen.value = false
|
||||
isPrivateOptionTapped.value = false
|
||||
},
|
||||
shouldBePrivate = isPrivateOptionTapped.value,
|
||||
onCreateList = { name, description ->
|
||||
onSetCreate(name, isPrivateOptionTapped.value, description)
|
||||
},
|
||||
@@ -528,7 +575,11 @@ fun FollowSetCreationItem(
|
||||
}
|
||||
Spacer(modifier = StdVertSpacer)
|
||||
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,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
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_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_type_description">Ikona pro seznam %1$s</string>
|
||||
<string name="follow_set_presence_indicator">"%1$s je v tomto seznamu"</string>
|
||||
<string name="follow_set_icon_description">Ikona pro seznam %1$s</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_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_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_type_description">Ikona pro seznam %1$s</string>
|
||||
<string name="follow_set_presence_indicator">%1$s je v tomto seznamu</string>
|
||||
<string name="follow_set_icon_description">Ikona pro seznam %1$s</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_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_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_type_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_icon_description">Symbol für %1$s-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_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_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_type_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_icon_description">Symbol für %1$s-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_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 name="follow_set_add_author_from_note_action">लेखक जोडें अनुगम्य सूची में</string>
|
||||
<string name="follow_set_profile_actions_menu_description">प्रयोक्ता को सूचियों में जोडें अथवा हटाएँ अथवा इस प्रयोक्ता के साथ नई सूची बनाएँ।</string>
|
||||
<string name="follow_set_type_description">सूची %1$s के लिए चिह्न</string>
|
||||
<string name="follow_set_presence_indicator">"%1$s इस सूची में है"</string>
|
||||
<string name="follow_set_icon_description">सूची %1$s के लिए चिह्न</string>
|
||||
<string name="follow_set_absence_indicator">"%1$s इस सूची में नहीं है"</string>
|
||||
<string name="follow_set_man_dialog_title">आपके अनुगम्य सूचियाँ</string>
|
||||
<string name="follow_set_empty_dialog_msg">कोई अनुगम्य सूचियाँ प्राप्त नहीं। अथवा आपका कोई अनुगम्य सूचियाँ हैं नहीं। नवीकरण के लिए नीचे दबाएँ अथवा विकल्पसूची द्वारा एक नया बनाएँ।</string>
|
||||
|
||||
@@ -457,8 +457,7 @@
|
||||
</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_type_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_icon_description">Ikon a(z) %1$s nevű listához</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_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="no">Nē</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_dialog_title">Jaunais %1$s saraksts</string>
|
||||
<string name="follow_set_creation_name_label">Kolekcijas nosaukums</string>
|
||||
|
||||
@@ -454,8 +454,7 @@
|
||||
</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_type_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_icon_description">Ikona dla listy %1$s</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_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_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_type_description">Ícone da lista %1$s</string>
|
||||
<string name="follow_set_presence_indicator">"%1$s está presente nesta lista"</string>
|
||||
<string name="follow_set_icon_description">Ícone da lista %1$s</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_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_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_type_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_icon_description">Ikon för %1$s-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_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 name="follow_set_add_author_from_note_action">添加作者到关注集</string>
|
||||
<string name="follow_set_profile_actions_menu_description">从列表中添加或删除用户,或用此用户创建一个新列表。</string>
|
||||
<string name="follow_set_type_description">%1$s 列表的图标</string>
|
||||
<string name="follow_set_presence_indicator">"此列表中有 %1$s"</string>
|
||||
<string name="follow_set_icon_description">%1$s 列表的图标</string>
|
||||
<string name="follow_set_absence_indicator">"此列表中没有 %1$s"</string>
|
||||
<string name="follow_set_man_dialog_title">您的关注集</string>
|
||||
<string name="follow_set_empty_dialog_msg">未找到关注集,或者你还没有任何关注集。轻按下方刷新,或使用按钮新建。</string>
|
||||
|
||||
@@ -519,22 +519,40 @@
|
||||
It seems you do not have any follow sets yet.
|
||||
\nTap below to refresh, or tap the add buttons to create a new one.
|
||||
</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_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_presence_indicator">"%1$s is present in this list"</string>
|
||||
<string name="follow_set_absence_indicator">"%1$s is not in this list"</string>
|
||||
<string name="follow_set_icon_description">Icon for follow set</string>
|
||||
<string name="follow_set_public_presence_indicator">%1$s is a public member</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_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_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_description">Creates a %1$s follow set, and adds %2$s to it.</string>
|
||||
<string name="follow_set_creation_dialog_title">New %1$s List</string>
|
||||
<string name="follow_set_creation_item_label">New list with %1$s membership</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 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_copy_name_label">(Original Set name)</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_copy_action_btn_label">Copy/Clone 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_second_part"> to..</string>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ languageId = "17.0.6"
|
||||
lazysodiumAndroid = "5.2.0"
|
||||
lazysodiumJava = "5.2.0"
|
||||
lifecycleRuntimeKtx = "2.9.4"
|
||||
lightcompressor = "1.5.0"
|
||||
lightcompressor-enhanced = "1.6.0"
|
||||
markdown = "f92ef49c9d"
|
||||
media3 = "1.8.0"
|
||||
mockk = "1.14.5"
|
||||
@@ -63,7 +63,7 @@ core = "1.7.0"
|
||||
mavenPublish = "0.34.0"
|
||||
|
||||
[libraries]
|
||||
abedElazizShe-video-compressor-fork = { group = "com.github.davotoula", name = "LightCompressor-enhanced", version.ref = "lightcompressor" }
|
||||
abedElazizShe-video-compressor-fork = { group = "com.github.davotoula", name = "LightCompressor-enhanced", version.ref = "lightcompressor-enhanced" }
|
||||
accompanist-adaptive = { group = "com.google.accompanist", name = "accompanist-adaptive", version.ref = "accompanistAdaptive" }
|
||||
accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanistAdaptive" }
|
||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||
|
||||
@@ -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(
|
||||
publicTags: TagArray,
|
||||
privateTags: TagArray,
|
||||
@@ -223,51 +248,61 @@ class PeopleListEvent(
|
||||
title: String,
|
||||
description: String? = null,
|
||||
isPrivate: Boolean,
|
||||
firstMemberHex: String? = null,
|
||||
firstPublicMembers: List<String> = emptyList(),
|
||||
firstPrivateMembers: List<String> = emptyList(),
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (PeopleListEvent) -> Unit,
|
||||
) {
|
||||
if (description == null) {
|
||||
val newList =
|
||||
create(
|
||||
val newListTemplate =
|
||||
build(
|
||||
name = title,
|
||||
person = UserTag(pubKey = firstMemberHex ?: signer.pubKey),
|
||||
isPrivate = isPrivate,
|
||||
publicPeople =
|
||||
if (!isPrivate && firstPublicMembers.isNotEmpty()) {
|
||||
firstPublicMembers.map { UserTag(pubKey = it) }
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
privatePeople =
|
||||
if (isPrivate && firstPrivateMembers.isNotEmpty()) {
|
||||
firstPrivateMembers.map { UserTag(pubKey = it) }
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
signer = signer,
|
||||
dTag = dTag,
|
||||
createdAt = createdAt,
|
||||
)
|
||||
) {
|
||||
if (description != null) addUnique(DescriptionTag.assemble(description))
|
||||
}
|
||||
val newList = signer.sign(newListTemplate)
|
||||
onReady(newList)
|
||||
} else {
|
||||
if (isPrivate) {
|
||||
val event =
|
||||
}
|
||||
|
||||
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,
|
||||
privatePeople = listOf(UserTag(pubKey = firstMemberHex ?: signer.pubKey)),
|
||||
publicPeople = firstPublicMembers.map { UserTag(pubKey = it) },
|
||||
privatePeople = firstPrivateMembers.map { UserTag(pubKey = it) },
|
||||
signer = signer,
|
||||
dTag = dTag,
|
||||
createdAt = createdAt,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
if (description != null) addUnique(DescriptionTag.assemble(description))
|
||||
}
|
||||
|
||||
val listClone = signer.sign(cloneTemplate)
|
||||
onReady(listClone)
|
||||
}
|
||||
|
||||
suspend fun createListWithUser(
|
||||
@@ -311,6 +346,7 @@ class PeopleListEvent(
|
||||
suspend fun removeUser(
|
||||
earlierVersion: PeopleListEvent,
|
||||
pubKeyHex: String,
|
||||
isUserPrivate: Boolean,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (PeopleListEvent) -> Unit,
|
||||
@@ -319,6 +355,7 @@ class PeopleListEvent(
|
||||
remove(
|
||||
earlierVersion = earlierVersion,
|
||||
person = UserTag(pubKey = pubKeyHex),
|
||||
isPrivate = isUserPrivate,
|
||||
signer = signer,
|
||||
createdAt = createdAt,
|
||||
)
|
||||
@@ -351,5 +388,51 @@ class PeopleListEvent(
|
||||
)
|
||||
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