feat(communities): prefer name() tag + add edit flow
Name display - CommunityCard, CommunityName spinner option, FeedFilterSpinner renderer, and DisplayFollowingCommunityInPost now all prefer the NIP-72 `name` tag and fall back to the `d` tag. Previously they rendered the raw UUID d-tag for communities created by this app, which made them show up as /n/<hex> instead of the configured name. - DisplayCommunity is now reactive via observeNote so the chip in NoteCompose recomposes when the community definition arrives. Edit flow - New Route.EditCommunity(kind, pubKeyHex, dTag) and EditCommunityScreen that reuses the create form via a shared CommunityFormScreen composable. - NewCommunityModel gains loadFrom(CommunityDefinitionEvent) to preload name/description/rules/moderators/relays and to remember the existing d-tag + image URL. publish() reuses the existing d-tag so the replaceable kind-34550 event updates in place. - Cover preview on the form supports the already-uploaded image (AsyncImage with remove button) and falls back to the placeholder if cleared. - Owner (note author == signer) sees an "Edit" FilledTonalIconButton in LongCommunityActionOptions that opens the edit screen. - Top bar becomes "Edit Community" with a Save button when editing.
This commit is contained in:
@@ -94,6 +94,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessGameScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessLobbyScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.CommunityScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.list.CommunitiesScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.newCommunity.EditCommunityScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.newCommunity.NewCommunityScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.LongFormPostScreen
|
||||
@@ -225,6 +226,7 @@ fun BuildNavigation(
|
||||
composableFromEnd<Route.Polls> { PollsScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.Communities> { CommunitiesScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.NewCommunity> { NewCommunityScreen(accountViewModel, nav) }
|
||||
composableFromEndArgs<Route.EditCommunity> { EditCommunityScreen(Address(it.kind, it.pubKeyHex, it.dTag), accountViewModel, nav) }
|
||||
composableFromEnd<Route.Badges> { BadgesScreen(accountViewModel, nav) }
|
||||
composableFromEnd<Route.ProfileBadges> { ProfileBadgesScreen(accountViewModel, nav) }
|
||||
composableFromBottomArgs<Route.AwardBadge> { AwardBadgeScreen(it.kind, it.pubKeyHex, it.dTag, accountViewModel, nav) }
|
||||
|
||||
@@ -49,6 +49,18 @@ sealed class Route {
|
||||
|
||||
@Serializable object NewCommunity : Route()
|
||||
|
||||
@Serializable data class EditCommunity(
|
||||
val kind: Int,
|
||||
val pubKeyHex: HexKey,
|
||||
val dTag: String,
|
||||
) : Route() {
|
||||
constructor(address: Address) : this(
|
||||
kind = address.kind,
|
||||
pubKeyHex = address.pubKeyHex,
|
||||
dTag = address.dTag,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object Badges : Route()
|
||||
|
||||
@Serializable object ProfileBadges : Route()
|
||||
|
||||
+5
-1
@@ -104,6 +104,7 @@ import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.nip51Lists.followList.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@@ -351,7 +352,10 @@ fun RenderOption(
|
||||
is CommunityName -> {
|
||||
val it by observeNote(option.note, accountViewModel)
|
||||
|
||||
Text(text = "/n/${((it.note as? AddressableNote)?.dTag() ?: "")}", fontSize = Font14SP, color = MaterialTheme.colorScheme.onSurface)
|
||||
val addressable = it.note as? AddressableNote
|
||||
val definition = addressable?.event as? CommunityDefinitionEvent
|
||||
val label = definition?.name()?.ifBlank { null } ?: addressable?.dTag() ?: ""
|
||||
Text(text = "/n/$label", fontSize = Font14SP, color = MaterialTheme.colorScheme.onSurface)
|
||||
}
|
||||
|
||||
is RelayName -> {
|
||||
|
||||
+23
-11
@@ -24,9 +24,12 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote
|
||||
import com.vitorpamplona.amethyst.ui.components.ClickableTextColor
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
@@ -34,6 +37,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
|
||||
@Composable
|
||||
fun DisplayFollowingCommunityInPost(
|
||||
@@ -42,38 +46,46 @@ fun DisplayFollowingCommunityInPost(
|
||||
nav: INav,
|
||||
) {
|
||||
Column(HalfStartPadding) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) { DisplayCommunity(baseNote, nav) }
|
||||
Row(verticalAlignment = Alignment.CenterVertically) { DisplayCommunity(baseNote, accountViewModel, nav) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DisplayCommunity(
|
||||
note: Note,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val communityTag = note.event?.communityAddress() ?: return
|
||||
|
||||
val communityNote = LocalCache.getOrCreateAddressableNote(communityTag)
|
||||
val communityState by observeNote(communityNote, accountViewModel)
|
||||
val label = communityShortLabel(communityTag, communityState.note.event as? CommunityDefinitionEvent)
|
||||
|
||||
ClickableTextColor(
|
||||
getCommunityShortName(communityTag),
|
||||
label,
|
||||
linkColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.52f),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
) {
|
||||
nav.nav {
|
||||
note.event?.communityAddress()?.let { communityTag ->
|
||||
Route.Community(communityTag.kind, communityTag.pubKeyHex, communityTag.dTag)
|
||||
note.event?.communityAddress()?.let { addr ->
|
||||
Route.Community(addr.kind, addr.pubKeyHex, addr.dTag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCommunityShortName(communityAddress: Address): String {
|
||||
val name =
|
||||
if (communityAddress.dTag.length > 10) {
|
||||
communityAddress.dTag.take(10) + "..."
|
||||
private fun communityShortLabel(
|
||||
address: Address,
|
||||
definition: CommunityDefinitionEvent?,
|
||||
): String {
|
||||
val raw = definition?.name()?.ifBlank { null } ?: address.dTag
|
||||
val shortened =
|
||||
if (raw.length > 12) {
|
||||
raw.take(12) + "..."
|
||||
} else {
|
||||
communityAddress.dTag.take(10)
|
||||
raw
|
||||
}
|
||||
|
||||
return "/n/$name"
|
||||
return "/n/$shortened"
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material.icons.outlined.Edit
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
@@ -70,6 +71,7 @@ import com.vitorpamplona.amethyst.ui.components.RichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
||||
@@ -439,6 +441,9 @@ private fun LongCommunityActionOptions(
|
||||
nav: INav,
|
||||
) {
|
||||
Row {
|
||||
if (note.author?.pubkeyHex == accountViewModel.account.signer.pubKey) {
|
||||
EditCommunityButton(note, nav)
|
||||
}
|
||||
ShareCommunityButton(accountViewModel, note, nav)
|
||||
WatchAddressableNoteFollows(note, accountViewModel) { isFollowing ->
|
||||
if (isFollowing) {
|
||||
@@ -448,6 +453,22 @@ private fun LongCommunityActionOptions(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EditCommunityButton(
|
||||
note: AddressableNote,
|
||||
nav: INav,
|
||||
) {
|
||||
FilledTonalIconButton(
|
||||
onClick = { nav.nav(Route.EditCommunity(note.address)) },
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Edit,
|
||||
modifier = Size18Modifier,
|
||||
contentDescription = stringRes(R.string.edit_community),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WatchAddressableNoteFollows(
|
||||
note: AddressableNote,
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import com.vitorpamplona.quartz.nip51Lists.followList.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.interestSet.InterestSetEvent
|
||||
import com.vitorpamplona.quartz.nip51Lists.peopleList.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -381,7 +382,11 @@ class PeopleListName(
|
||||
class CommunityName(
|
||||
val note: AddressableNote,
|
||||
) : Name() {
|
||||
override fun name() = "/n/${(note.dTag())}"
|
||||
override fun name(): String {
|
||||
val definition = note.event as? CommunityDefinitionEvent
|
||||
val label = definition?.name()?.ifBlank { null } ?: note.dTag()
|
||||
return "/n/$label"
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
||||
+58
-10
@@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.ModeratorTag
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.RelayTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -61,11 +62,16 @@ data class CommunityRelayEntry(
|
||||
class NewCommunityModel : ViewModel() {
|
||||
var account: Account? = null
|
||||
|
||||
// When set, publish() reuses this d-tag so the kind-34550 replaceable event
|
||||
// is updated in place (edit flow). Null in the create flow.
|
||||
var existingDTag: String? = null
|
||||
|
||||
var isPublishing by mutableStateOf(false)
|
||||
|
||||
var name by mutableStateOf("")
|
||||
var description by mutableStateOf("")
|
||||
var rules by mutableStateOf("")
|
||||
var existingImageUrl by mutableStateOf<String?>(null)
|
||||
|
||||
// Image upload state - mirrors NewBadgeModel.
|
||||
var multiOrchestrator by mutableStateOf<MultiOrchestrator?>(null)
|
||||
@@ -88,6 +94,42 @@ class NewCommunityModel : ViewModel() {
|
||||
this.stripMetadata = account.settings.stripLocationOnUpload
|
||||
}
|
||||
|
||||
/**
|
||||
* Preloads the form with the current contents of [existing] so the user can edit
|
||||
* the replaceable kind 34550 event. Keeps the original `d` tag so relays replace
|
||||
* the previous version instead of creating a new community.
|
||||
*/
|
||||
fun loadFrom(existing: CommunityDefinitionEvent) {
|
||||
existingDTag = existing.dTag()
|
||||
name = existing.name().orEmpty()
|
||||
description = existing.description().orEmpty()
|
||||
rules = existing.rules().orEmpty()
|
||||
existingImageUrl = existing.image()?.imageUrl
|
||||
|
||||
val ownerKey = account?.signer?.pubKey
|
||||
moderators.clear()
|
||||
existing
|
||||
.moderatorKeys()
|
||||
.asSequence()
|
||||
.filter { it != ownerKey }
|
||||
.distinct()
|
||||
.forEach { pubkey ->
|
||||
val user = account?.cache?.getOrCreateUser(pubkey) ?: return@forEach
|
||||
if (moderators.none { it.pubkeyHex == user.pubkeyHex }) {
|
||||
moderators.add(user)
|
||||
}
|
||||
}
|
||||
|
||||
relays.clear()
|
||||
existing.relays().forEach { tag ->
|
||||
if (relays.none { it.url == tag.url }) {
|
||||
relays.add(CommunityRelayEntry(tag.url, tag.marker))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isEditing(): Boolean = existingDTag != null
|
||||
|
||||
fun defaultServer() = account?.settings?.defaultFileServer ?: DEFAULT_MEDIA_SERVERS[0]
|
||||
|
||||
fun setPickedMedia(uris: ImmutableList<SelectedMedia>) {
|
||||
@@ -156,15 +198,15 @@ class NewCommunityModel : ViewModel() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
isPublishing = true
|
||||
try {
|
||||
val imageUrl =
|
||||
uploadImageIfAny(context, myAccount, onError) ?: run {
|
||||
if (multiOrchestrator != null) {
|
||||
// upload failed; error was reported, bail out
|
||||
return@launch
|
||||
}
|
||||
val uploadedUrl =
|
||||
if (multiOrchestrator != null) {
|
||||
uploadImageIfAny(context, myAccount, onError) ?: return@launch
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val imageUrl = uploadedUrl ?: existingImageUrl
|
||||
|
||||
val ownerKey = myAccount.signer.pubKey
|
||||
val moderatorTags =
|
||||
buildList {
|
||||
@@ -177,6 +219,8 @@ class NewCommunityModel : ViewModel() {
|
||||
|
||||
val relayTags = relays.map { RelayTag(it.url, it.marker) }
|
||||
|
||||
val dTag = existingDTag ?: Uuid.random().toString()
|
||||
|
||||
val definition =
|
||||
myAccount.sendCommunityDefinition(
|
||||
name = name.trim(),
|
||||
@@ -185,7 +229,7 @@ class NewCommunityModel : ViewModel() {
|
||||
image = imageUrl,
|
||||
rules = rules.trim().ifBlank { null },
|
||||
relays = relayTags.ifEmpty { null },
|
||||
dTag = Uuid.random().toString(),
|
||||
dTag = dTag,
|
||||
)
|
||||
|
||||
if (definition == null) {
|
||||
@@ -196,9 +240,11 @@ class NewCommunityModel : ViewModel() {
|
||||
return@launch
|
||||
}
|
||||
|
||||
// Follow it so it shows up under "Mine".
|
||||
val communityNote = myAccount.cache.getOrCreateAddressableNote(definition.address())
|
||||
myAccount.follow(communityNote)
|
||||
// Auto-follow only on the create flow; editing doesn't change the follow set.
|
||||
if (existingDTag == null) {
|
||||
val communityNote = myAccount.cache.getOrCreateAddressableNote(definition.address())
|
||||
myAccount.follow(communityNote)
|
||||
}
|
||||
|
||||
selectedServer?.let { myAccount.settings.changeDefaultFileServer(it) }
|
||||
myAccount.settings.changeStripLocationOnUpload(stripMetadata)
|
||||
@@ -260,6 +306,8 @@ class NewCommunityModel : ViewModel() {
|
||||
name = ""
|
||||
description = ""
|
||||
rules = ""
|
||||
existingImageUrl = null
|
||||
existingDTag = null
|
||||
multiOrchestrator = null
|
||||
moderators.clear()
|
||||
relays.clear()
|
||||
|
||||
+126
-26
@@ -71,14 +71,17 @@ import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import coil3.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.Nip05State
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.StrippingFailureDialog
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.ActionTopBar
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.CreatingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.note.UserPicture
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList
|
||||
@@ -89,14 +92,30 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditF
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.SuggestionListDefaultHeightPage
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.RelayTag
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NewCommunityScreen(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = CommunityFormScreen(editing = null, accountViewModel = accountViewModel, nav = nav)
|
||||
|
||||
@Composable
|
||||
fun EditCommunityScreen(
|
||||
editing: Address,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) = CommunityFormScreen(editing = editing, accountViewModel = accountViewModel, nav = nav)
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CommunityFormScreen(
|
||||
editing: Address?,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val model: NewCommunityModel = viewModel()
|
||||
val context = LocalContext.current
|
||||
@@ -105,6 +124,15 @@ fun NewCommunityScreen(
|
||||
model.init(accountViewModel.account)
|
||||
}
|
||||
|
||||
LaunchedEffect(editing) {
|
||||
if (editing != null) {
|
||||
val note = LocalCache.getOrCreateAddressableNote(editing)
|
||||
(note.event as? CommunityDefinitionEvent)?.let { model.loadFrom(it) }
|
||||
} else {
|
||||
model.existingDTag = null
|
||||
}
|
||||
}
|
||||
|
||||
StrippingFailureDialog(model.strippingFailureConfirmation)
|
||||
|
||||
var wantsToPickImage by remember { mutableStateOf(false) }
|
||||
@@ -122,21 +150,42 @@ fun NewCommunityScreen(
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CreatingTopBar(
|
||||
titleRes = R.string.new_community,
|
||||
isActive = model::canPost,
|
||||
onCancel = {
|
||||
model.reset()
|
||||
nav.popBack()
|
||||
},
|
||||
onPost = {
|
||||
model.publish(
|
||||
context = context,
|
||||
onSuccess = { nav.popBack() },
|
||||
onError = accountViewModel.toastManager::toast,
|
||||
)
|
||||
},
|
||||
)
|
||||
val titleRes =
|
||||
if (model.isEditing()) R.string.edit_community else R.string.new_community
|
||||
if (model.isEditing()) {
|
||||
ActionTopBar(
|
||||
titleRes = titleRes,
|
||||
postRes = R.string.save,
|
||||
isActive = model::canPost,
|
||||
onCancel = {
|
||||
model.reset()
|
||||
nav.popBack()
|
||||
},
|
||||
onPost = {
|
||||
model.publish(
|
||||
context = context,
|
||||
onSuccess = { nav.popBack() },
|
||||
onError = accountViewModel.toastManager::toast,
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
CreatingTopBar(
|
||||
titleRes = titleRes,
|
||||
isActive = model::canPost,
|
||||
onCancel = {
|
||||
model.reset()
|
||||
nav.popBack()
|
||||
},
|
||||
onPost = {
|
||||
model.publish(
|
||||
context = context,
|
||||
onSuccess = { nav.popBack() },
|
||||
onError = accountViewModel.toastManager::toast,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
) { pad ->
|
||||
Surface(
|
||||
@@ -235,18 +284,69 @@ private fun CommunityImagePicker(
|
||||
accountViewModel: AccountViewModel,
|
||||
onPickImage: () -> Unit,
|
||||
) {
|
||||
if (model.hasPickedImage()) {
|
||||
model.multiOrchestrator?.let {
|
||||
Box(modifier = Modifier.clickable(onClick = onPickImage)) {
|
||||
ShowImageUploadGallery(
|
||||
list = it,
|
||||
onDelete = { model.setPickedMedia(persistentListOf()) },
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
val existingUrl = model.existingImageUrl
|
||||
when {
|
||||
model.hasPickedImage() -> {
|
||||
model.multiOrchestrator?.let {
|
||||
Box(modifier = Modifier.clickable(onClick = onPickImage)) {
|
||||
ShowImageUploadGallery(
|
||||
list = it,
|
||||
onDelete = { model.setPickedMedia(persistentListOf()) },
|
||||
accountViewModel = accountViewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CommunityImagePlaceholder(onClick = onPickImage)
|
||||
|
||||
!existingUrl.isNullOrBlank() -> {
|
||||
ExistingCommunityCover(
|
||||
url = existingUrl,
|
||||
onClick = onPickImage,
|
||||
onClear = { model.existingImageUrl = null },
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
CommunityImagePlaceholder(onClick = onPickImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExistingCommunityCover(
|
||||
url: String,
|
||||
onClick: () -> Unit,
|
||||
onClear: () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(16f / 9f)
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.TopEnd,
|
||||
) {
|
||||
AsyncImage(
|
||||
model = url,
|
||||
contentDescription = null,
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(16f / 9f)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.outline,
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
),
|
||||
)
|
||||
IconButton(onClick = onClear) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Close,
|
||||
contentDescription = stringRes(R.string.remove),
|
||||
tint = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ fun RenderCommunitiesThumb(
|
||||
|
||||
RenderCommunitiesThumb(
|
||||
CommunityCard(
|
||||
name = noteEvent.dTag(),
|
||||
name = noteEvent.name()?.ifBlank { null } ?: noteEvent.dTag(),
|
||||
description = noteEvent.description(),
|
||||
cover = noteEvent.image()?.imageUrl,
|
||||
moderators = noteEvent.moderatorKeys().toImmutableList(),
|
||||
|
||||
@@ -422,6 +422,7 @@
|
||||
<string name="badges">Badges</string>
|
||||
<string name="communities">Communities</string>
|
||||
<string name="new_community">New Community</string>
|
||||
<string name="edit_community">Edit Community</string>
|
||||
<string name="new_community_name">Community name</string>
|
||||
<string name="new_community_description">Description</string>
|
||||
<string name="new_community_rules">Rules (optional)</string>
|
||||
|
||||
Reference in New Issue
Block a user