diff --git a/README.md b/README.md index 851ee8374..1b6b6ac5d 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,6 @@ alt="Get it on Obtaininum" height="70">](https://github.com/ImranR98/Obtainium) [Get it on GitHub](https://github.com/vitorpamplona/amethyst/releases) -[Get it on F-Droid](https://f-droid.org/packages/com.vitorpamplona.amethyst/) [Get it on Google Play](https://play.google.com/store/apps/details?id=com.vitorpamplona.amethyst) diff --git a/amethyst/google-services.json b/amethyst/google-services.json index 31ca5e8ec..dbdb975e5 100644 --- a/amethyst/google-services.json +++ b/amethyst/google-services.json @@ -2,7 +2,7 @@ "project_info": { "project_number": "768341258853", "project_id": "amethyst-3057a", - "storage_bucket": "amethyst-3057a.appspot.com" + "storage_bucket": "amethyst-3057a.firebasestorage.app" }, "client": [ { @@ -34,6 +34,35 @@ } } }, + { + "client_info": { + "mobilesdk_app_id": "1:768341258853:android:3270514bee61de546b8c8c", + "android_client_info": { + "package_name": "com.vitorpamplona.amethyst.benchmark" + } + }, + "oauth_client": [ + { + "client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyB7ZxgdpgrN6R223HCFdfv4ulP8Egp7trE" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + }, { "client_info": { "mobilesdk_app_id": "1:768341258853:android:e0200680f552484d6b8c8c", @@ -62,35 +91,6 @@ ] } } - }, - { - "client_info": { - "mobilesdk_app_id": "1:768341258853:android:5d07c35a37b24ff36b8c8c", - "android_client_info": { - "package_name": "com.vitorpamplona.amethyst.benchmark" - } - }, - "oauth_client": [ - { - "client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyB7ZxgdpgrN6R223HCFdfv4ulP8Egp7trE" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [ - { - "client_id": "768341258853-6um8ig59qstvio60gfo60fe5e45lnqqe.apps.googleusercontent.com", - "client_type": 3 - } - ] - } - } } ], "configuration_version": "1" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index a965161d4..ab8bb52bf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -127,8 +127,6 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip19Bech32.entities.NRelay import com.vitorpamplona.quartz.nip19Bech32.entities.NSec import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent -import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent -import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag import com.vitorpamplona.quartz.nip30CustomEmoji.emojis import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent @@ -2313,10 +2311,29 @@ class Account( fun signAndSendPrivately( template: EventTemplate, relayList: List, + onDone: (T) -> Unit = {}, ) { signer.sign(template) { LocalCache.justConsume(it, null) Amethyst.instance.client.sendPrivately(it, relayList = convertRelayList(relayList)) + onDone(it) + } + } + + fun signAndSendPrivatelyOrBroadcast( + template: EventTemplate, + relayList: (T) -> List?, + onDone: (T) -> Unit = {}, + ) { + signer.sign(template) { + LocalCache.justConsume(it, null) + val relays = relayList(it) + if (relays != null) { + Amethyst.instance.client.sendPrivately(it, relayList = convertRelayList(relays)) + } else { + Amethyst.instance.client.send(it) + } + onDone(it) } } @@ -2806,28 +2823,6 @@ class Account( } } - fun sendChangeChannel(template: EventTemplate) { - if (!isWriteable()) return - - signer.sign(template) { - Amethyst.instance.client.send(it) - LocalCache.justConsume(it, null) - - it.channelId()?.let { LocalCache.getChannelIfExists(it)?.let { follow(it) } } - } - } - - fun sendCreateNewChannel(template: EventTemplate) { - if (!isWriteable()) return - - signer.sign(template) { - Amethyst.instance.client.send(it) - LocalCache.justConsume(it, null) - - LocalCache.getChannelIfExists(it.id)?.let { follow(it) } - } - } - fun updateStatus( oldStatus: AddressableNote, newStatus: String, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt index a7dd1365f..442e44fb0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt @@ -33,9 +33,13 @@ import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList +import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress +import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent import com.vitorpamplona.quartz.nip19Bech32.toNEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent +import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent import com.vitorpamplona.quartz.nip28PublicChat.base.ChannelData import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent import com.vitorpamplona.quartz.utils.Hex @@ -46,17 +50,30 @@ class PublicChatChannel( idHex: String, ) : Channel(idHex) { var event: ChannelCreateEvent? = null + var infoTags = EmptyTagList var info = ChannelData(null, null, null, null) override fun relays() = info.relays ?: super.relays() + fun toNEvent() = NEvent.create(idHex, event?.pubKey, ChannelCreateEvent.KIND, *relays().toTypedArray()) + + fun toNostrUri() = "nostr:${toNEvent()}" + fun updateChannelInfo( creator: User, - channelInfo: ChannelCreateEvent, - updatedAt: Long, + event: ChannelCreateEvent, ) { - this.event = channelInfo - updateChannelInfo(creator, channelInfo.channelInfo(), updatedAt) + this.event = event + this.infoTags = event.tags.toImmutableListOfLists() + updateChannelInfo(creator, event.channelInfo(), event.createdAt) + } + + fun updateChannelInfo( + creator: User, + event: ChannelMetadataEvent, + ) { + this.infoTags = event.tags.toImmutableListOfLists() + updateChannelInfo(creator, event.channelInfo(), event.createdAt) } fun updateChannelInfo( @@ -116,9 +133,11 @@ class LiveActivitiesChannel( .filter { it.contains(prefix, true) } .isNotEmpty() - fun toNAddr() = NAddress.create(address.kind, address.pubKeyHex, address.dTag, relayHintUrl()) + fun toNAddr() = NAddress.create(address.kind, address.pubKeyHex, address.dTag, *relays().toTypedArray()) fun toATag() = ATag(address, relayHintUrl()) + + fun toNostrUri() = "nostr:${toNAddr()}" } data class Counter( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 048aabbd6..dc20a6211 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -1385,7 +1385,7 @@ object LocalCache { } if (oldChannel.creator == null || oldChannel.creator == author) { if (oldChannel is PublicChatChannel) { - oldChannel.updateChannelInfo(author, event, event.createdAt) + oldChannel.updateChannelInfo(author, event) } } } @@ -1404,7 +1404,7 @@ object LocalCache { val author = getOrCreateUser(event.pubKey) if (event.createdAt > oldChannel.updatedMetadataAt) { if (oldChannel is PublicChatChannel) { - oldChannel.updateChannelInfo(author, event.channelInfo(), event.createdAt) + oldChannel.updateChannelInfo(author, event) } } else { // Log.d("MT","Relay sent a previous Metadata Event ${oldUser.toBestDisplayName()} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 984781e74..56da5a61c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -29,8 +29,8 @@ import com.vitorpamplona.amethyst.launchAndWaitAll import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji +import com.vitorpamplona.amethyst.service.replace import com.vitorpamplona.amethyst.tryAndWait -import com.vitorpamplona.amethyst.ui.actions.relays.updated import com.vitorpamplona.amethyst.ui.note.combineWith import com.vitorpamplona.amethyst.ui.note.toShortenHex import com.vitorpamplona.ammolite.relays.BundledUpdate @@ -791,28 +791,28 @@ open class Note( // migrates these comments to a new version replies.forEach { note.addReply(it) - it.replyTo = it.replyTo?.updated(this, note) + it.replyTo = it.replyTo?.replace(this, note) } reactions.forEach { it.value.forEach { note.addReaction(it) - it.replyTo = it.replyTo?.updated(this, note) + it.replyTo = it.replyTo?.replace(this, note) } } boosts.forEach { note.addBoost(it) - it.replyTo = it.replyTo?.updated(this, note) + it.replyTo = it.replyTo?.replace(this, note) } reports.forEach { it.value.forEach { note.addReport(it) - it.replyTo = it.replyTo?.updated(this, note) + it.replyTo = it.replyTo?.replace(this, note) } } zaps.forEach { note.addZap(it.key, it.value) - it.key.replyTo = it.key.replyTo?.updated(this, note) - it.value?.replyTo = it.value?.replyTo?.updated(this, note) + it.key.replyTo = it.key.replyTo?.replace(this, note) + it.value?.replyTo = it.value?.replyTo?.replace(this, note) } replyTo = null diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ByteFormatter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ByteFormatter.kt new file mode 100644 index 000000000..d3eec1de6 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ByteFormatter.kt @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service + +import kotlin.math.roundToInt + +fun countToHumanReadableBytes(counter: Int) = + when { + counter >= 1000000000 -> "${(counter / 1000000000f).roundToInt()} GB" + counter >= 1000000 -> "${(counter / 1000000f).roundToInt()} MB" + counter >= 1000 -> "${(counter / 1000f).roundToInt()} KB" + else -> "$counter" + } + +fun countToHumanReadableBytes(counter: Long) = + when { + counter >= 1000000000 -> "${(counter / 1000000000f).roundToInt()} GB" + counter >= 1000000 -> "${(counter / 1000000f).roundToInt()} MB" + counter >= 1000 -> "${(counter / 1000f).roundToInt()} KB" + else -> "$counter" + } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/ByteFormatter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CountFormatter.kt similarity index 55% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/ByteFormatter.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/service/CountFormatter.kt index 9b3679c06..469173b69 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/ByteFormatter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/CountFormatter.kt @@ -18,31 +18,17 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.service -fun countToHumanReadableBytes(counter: Int) = - when { - counter >= 1000000000 -> "${Math.round(counter / 1000000000f)} GB" - counter >= 1000000 -> "${Math.round(counter / 1000000f)} MB" - counter >= 1000 -> "${Math.round(counter / 1000f)} KB" - else -> "$counter" - } - -fun countToHumanReadableBytes(counter: Long) = - when { - counter >= 1000000000 -> "${Math.round(counter / 1000000000f)} GB" - counter >= 1000000 -> "${Math.round(counter / 1000000f)} MB" - counter >= 1000 -> "${Math.round(counter / 1000f)} KB" - else -> "$counter" - } +import kotlin.math.roundToInt fun countToHumanReadable( counter: Int, str: String, ) = when { - counter >= 1000000000 -> "${Math.round(counter / 1000000000f)}G $str" - counter >= 1000000 -> "${Math.round(counter / 1000000f)}M $str" - counter >= 1000 -> "${Math.round(counter / 1000f)}K $str" + counter >= 1000000000 -> "${(counter / 1000000000f).roundToInt()}G $str" + counter >= 1000000 -> "${(counter / 1000000f).roundToInt()}M $str" + counter >= 1000 -> "${(counter / 1000f).roundToInt()}K $str" else -> "$counter $str" } @@ -50,8 +36,8 @@ fun countToHumanReadable( counter: Long, str: String, ) = when { - counter >= 1000000000 -> "${Math.round(counter / 1000000000f)}G $str" - counter >= 1000000 -> "${Math.round(counter / 1000000f)}M $str" - counter >= 1000 -> "${Math.round(counter / 1000f)}K $str" + counter >= 1000000000 -> "${(counter / 1000000000f).roundToInt()}G $str" + counter >= 1000000 -> "${(counter / 1000000f).roundToInt()}M $str" + counter >= 1000 -> "${(counter / 1000f).roundToInt()}K $str" else -> "$counter $str" } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/IterableExt.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/IterableExt.kt new file mode 100644 index 000000000..773b9c4b2 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/IterableExt.kt @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service + +fun Iterable.replace( + old: T, + new: T, +): List = map { if (it == old) new else it } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/SetExt.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/SetExt.kt new file mode 100644 index 000000000..03f1a5c35 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/SetExt.kt @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.service + +fun Set.togglePresenceInSet(item: T): Set = if (contains(item)) minus(item) else plus(item) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt index 2b3fc3378..75fc40dcc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt @@ -221,7 +221,7 @@ fun NewUserMetadataScreen( SelectSingleFromGallery( isUploading = postViewModel.isUploadingImageForBanner, tint = MaterialTheme.colorScheme.placeholderText, - modifier = Modifier.padding(start = 5.dp), + modifier = Modifier.padding(start = 5.dp).align(Alignment.CenterHorizontally), ) { postViewModel.uploadForBanner(it, context, onError = accountViewModel.toastManager::toast) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt index ed2b4b4ef..56191f447 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelaySelectionDialog.kt @@ -47,11 +47,11 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.Nip11Retriever -import com.vitorpamplona.amethyst.ui.actions.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/AllMediaServersLIstView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/AllMediaServersLIstView.kt index 8e42148ee..94812d35d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/AllMediaServersLIstView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/AllMediaServersLIstView.kt @@ -56,13 +56,13 @@ import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.ui.actions.relays.SettingsCategory -import com.vitorpamplona.amethyst.ui.actions.relays.SettingsCategoryWithButton import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategoryWithButton import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromGallery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromGallery.kt index 021dad965..b11e1a7cc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromGallery.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromGallery.kt @@ -24,7 +24,6 @@ import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddPhotoAlternate @@ -37,7 +36,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -110,22 +108,20 @@ private fun GallerySelectButton( modifier: Modifier, onClick: () -> Unit, ) { - Box { - IconButton( - modifier = modifier.align(Alignment.Center), - enabled = !isUploading, - onClick = { onClick() }, - ) { - if (!isUploading) { - Icon( - imageVector = Icons.Default.AddPhotoAlternate, - contentDescription = stringRes(id = R.string.upload_image), - modifier = Modifier.height(25.dp), - tint = tint, - ) - } else { - LoadingAnimation() - } + IconButton( + modifier = modifier, + enabled = !isUploading, + onClick = { onClick() }, + ) { + if (!isUploading) { + Icon( + imageVector = Icons.Default.AddPhotoAlternate, + contentDescription = stringRes(id = R.string.upload_image), + modifier = Modifier.height(25.dp), + tint = tint, + ) + } else { + LoadingAnimation() } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt index 3a8303793..19387ac97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/CashuRedeem.kt @@ -67,12 +67,12 @@ import com.vitorpamplona.amethyst.ui.note.ZapIcon import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.amethyst.ui.theme.CashuCardBorders import com.vitorpamplona.amethyst.ui.theme.Size18Modifier import com.vitorpamplona.amethyst.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.ui.theme.SmallishBorder import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers @@ -129,14 +129,12 @@ fun CashuPreviewPreview() { sharedPreferencesViewModel.init() sharedPreferencesViewModel.updateTheme(ThemeType.DARK) - AmethystTheme(sharedPrefsViewModel = sharedPreferencesViewModel) { - Column { - CashuPreviewNew( - token = CashuToken("token", "mint", 32400, listOf()), - melt = { token, context, onDone -> }, - toast = { title, message -> }, - ) - } + ThemeComparisonColumn { + CashuPreviewNew( + token = CashuToken("token", "mint", 32400, listOf()), + melt = { token, context, onDone -> }, + toast = { title, message -> }, + ) } } @@ -210,7 +208,6 @@ fun CashuPreviewNew( Text( "Redeem", - color = MaterialTheme.colorScheme.onBackground, fontSize = 16.sp, ) } @@ -232,7 +229,7 @@ fun CashuPreviewNew( shape = SmallishBorder, contentPadding = PaddingValues(0.dp), ) { - OpenInNewIcon(Size18Modifier, tint = MaterialTheme.colorScheme.onBackground) + OpenInNewIcon(Size18Modifier) } Spacer(modifier = StdHorzSpacer) @@ -245,7 +242,7 @@ fun CashuPreviewNew( shape = SmallishBorder, contentPadding = PaddingValues(0.dp), ) { - CopyIcon(Size18Modifier, tint = MaterialTheme.colorScheme.onBackground) + CopyIcon(Size18Modifier) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt index ac331bde1..1eb8b4454 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ExpandableRichTextViewer.kt @@ -26,9 +26,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState @@ -50,7 +48,6 @@ import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding import com.vitorpamplona.amethyst.ui.theme.StdTopPadding -import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists object ShowFullTextCache { @@ -128,16 +125,12 @@ fun ExpandableRichTextViewer( @Composable fun ShowMoreButton(onClick: () -> Unit) { - Button( + FilledTonalButton( modifier = StdTopPadding, onClick = onClick, shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.secondaryButtonBackground, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.show_more), color = Color.White) + Text(text = stringRes(R.string.show_more)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt index 356c9ec3f..b73e56957 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt @@ -32,8 +32,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.rounded.Warning -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text @@ -161,19 +160,14 @@ fun ContentWarningNote(onDismiss: () -> Unit) { } Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { - Button( + FilledTonalButton( modifier = Modifier.padding(top = 10.dp), onClick = onDismiss, shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { Text( text = stringRes(R.string.show_anyway), - color = Color.White, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt index 58c139aae..0d9622e37 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListKnownFeedFilter.kt @@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.actions.relays.updated +import com.vitorpamplona.amethyst.service.replace import com.vitorpamplona.quartz.nip01Core.tags.events.taggedEventIds import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable @@ -90,7 +90,7 @@ class ChatroomListKnownFeedFilter( if (newNotePair.key == oldNote.channelHex()) { hasUpdated = true if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) { - myNewList = myNewList.updated(oldNote, newNotePair.value) + myNewList = myNewList.replace(oldNote, newNotePair.value) } } } @@ -107,7 +107,7 @@ class ChatroomListKnownFeedFilter( if (newNotePair.key == oldRoom) { hasUpdated = true if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) { - myNewList = myNewList.updated(oldNote, newNotePair.value) + myNewList = myNewList.replace(oldNote, newNotePair.value) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt index faee792da..1746610ae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/ChatroomListNewFeedFilter.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.dal import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.actions.relays.updated +import com.vitorpamplona.amethyst.service.replace import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable @@ -77,7 +77,7 @@ class ChatroomListNewFeedFilter( if (newNotePair.key == oldRoom) { hasUpdated = true if ((newNotePair.value.createdAt() ?: 0) > (oldNote.createdAt() ?: 0)) { - myNewList = myNewList.updated(oldNote, newNotePair.value) + myNewList = myNewList.replace(oldNote, newNotePair.value) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index bc76d11a3..6a5980cca 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -47,7 +47,6 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen -import com.vitorpamplona.amethyst.ui.actions.relays.AllRelayListView import com.vitorpamplona.amethyst.ui.components.DisplayNotifyMessages import com.vitorpamplona.amethyst.ui.components.getActivity import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages @@ -61,6 +60,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarks.BookmarkListScree import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomByAuthorScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ChannelScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.MessagesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.CommunityScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen @@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.HomeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.ListsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen @@ -269,6 +270,22 @@ fun AppNavigation( ) } + composable( + Route.ChannelMetadataEdit.route, + Route.ChannelMetadataEdit.arguments, + enterTransition = { slideInVerticallyFromBottom }, + exitTransition = { scaleOut }, + popEnterTransition = { scaleIn }, + popExitTransition = { slideOutVerticallyToBottom }, + content = { + ChannelMetadataScreen( + channelId = it.id(), + accountViewModel = accountViewModel, + nav = nav, + ) + }, + ) + composable( Route.Event.route, Route.Event.arguments, @@ -313,7 +330,7 @@ fun AppNavigation( content = { val relayToAdd = it.arguments?.getString("toAdd") - AllRelayListView( + AllRelayListScreen( relayToAdd = relayToAdd, accountViewModel = accountViewModel, nav = nav, @@ -329,7 +346,7 @@ fun AppNavigation( popEnterTransition = { scaleIn }, popExitTransition = { slideOutVerticallyToBottom }, ) { - val draftMessage = it.arguments?.getString("message")?.ifBlank { null } + val draftMessage = it.message()?.ifBlank { null } val attachment = it.arguments?.getString("attachment")?.ifBlank { null }?.let { Uri.parse(it) @@ -339,8 +356,8 @@ fun AppNavigation( val fork = it.arguments?.getString("fork") val version = it.arguments?.getString("version") val draft = it.arguments?.getString("draft") - val enableMessageInterface = it.arguments?.getBoolean("enableMessageInterface") ?: false - val enableGeolocation = it.arguments?.getBoolean("enableGeolocation") ?: false + val enableMessageInterface = it.arguments?.getBoolean("enableMessageInterface") == true + val enableGeolocation = it.arguments?.getBoolean("enableGeolocation") == true NewPostScreen( message = draftMessage, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 56c1733b7..c7e9a269f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -213,6 +213,20 @@ sealed class Route( arguments = listOf(navArgument("id") { type = NavType.StringType }).toImmutableList(), ) + object ChannelMetadataEdit : + Route( + route = "ChannelMetadataEdit?id={id}", + icon = R.drawable.ic_moments, + arguments = + listOf( + navArgument("id") { + type = NavType.StringType + nullable = true + defaultValue = null + }, + ).toImmutableList(), + ) + object Event : Route( route = "Event/{id}", diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt index 29d165f84..23883c6cc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.navigation import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope @@ -30,6 +29,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.width import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -38,6 +39,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon +import com.vitorpamplona.amethyst.ui.theme.isLight @Composable fun TopBarExtensibleWithBackButton( @@ -90,11 +92,8 @@ fun MyExtensibleTopAppBar( ) if (expanded.value && extendableRow != null) { - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - ) { + val elevation = if (MaterialTheme.colorScheme.isLight) 1.dp else 10.dp + Surface(modifier = Modifier.fillMaxWidth(), tonalElevation = elevation) { Column { extendableRow() } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt index d656dd506..b98804779 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/BlankNote.kt @@ -26,9 +26,7 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -155,17 +153,13 @@ fun HiddenNote( } } - Button( + FilledTonalButton( modifier = Modifier.padding(top = 10.dp), onClick = onClick, shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - contentColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.show_anyway), color = Color.White) + Text(text = stringRes(R.string.show_anyway)) } } } @@ -201,17 +195,13 @@ fun HiddenNoteByMe( textAlign = TextAlign.Center, ) - Button( + FilledTonalButton( modifier = Modifier.padding(top = 10.dp), onClick = onClick, shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - contentColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.show_anyway), color = Color.White) + Text(text = stringRes(R.string.show_anyway)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt index b9120f386..79d2bbb95 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/Icons.kt @@ -312,27 +312,19 @@ fun CashuIcon(modifier: Modifier) { } @Composable -fun CopyIcon( - modifier: Modifier, - tint: Color = Color.Unspecified, -) { +fun CopyIcon(modifier: Modifier) { Icon( imageVector = Icons.Default.ContentCopy, stringRes(id = R.string.copy_to_clipboard), - tint = tint, modifier = modifier, ) } @Composable -fun OpenInNewIcon( - modifier: Modifier, - tint: Color = Color.Unspecified, -) { +fun OpenInNewIcon(modifier: Modifier) { Icon( imageVector = Icons.AutoMirrored.Filled.OpenInNew, stringRes(id = R.string.copy_to_clipboard), - tint = tint, modifier = modifier, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 38d0257c8..c92499b0a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -87,7 +87,6 @@ import com.vitorpamplona.amethyst.ui.note.types.EditState import com.vitorpamplona.amethyst.ui.note.types.EmptyState import com.vitorpamplona.amethyst.ui.note.types.FileHeaderDisplay import com.vitorpamplona.amethyst.ui.note.types.FileStorageHeaderDisplay -import com.vitorpamplona.amethyst.ui.note.types.JustVideoDisplay import com.vitorpamplona.amethyst.ui.note.types.PictureDisplay import com.vitorpamplona.amethyst.ui.note.types.RenderAppDefinition import com.vitorpamplona.amethyst.ui.note.types.RenderAudioHeader @@ -194,7 +193,6 @@ import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip68Picture.PictureEvent -import com.vitorpamplona.quartz.nip71Video.VideoEvent import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent @@ -328,9 +326,6 @@ fun AcceptableNote( ) } is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote) - is FileHeaderEvent -> FileHeaderDisplay(baseNote, false, ContentScale.FillWidth, accountViewModel) - is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, false, ContentScale.FillWidth, accountViewModel) - is VideoEvent -> JustVideoDisplay(baseNote, false, ContentScale.FillWidth, accountViewModel) else -> LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel) { showPopup -> CheckNewAndRenderNote( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt index b726e1a93..10db8440c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayCompose.kt @@ -25,9 +25,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf @@ -35,7 +34,6 @@ import androidx.compose.runtime.getValue 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.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -121,33 +119,25 @@ private fun RelayOptions( @Composable fun AddRelayButton(onClick: () -> Unit) { - Button( + OutlinedButton( modifier = Modifier.padding(horizontal = 3.dp), onClick = onClick, shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(id = R.string.add), color = Color.White) + Text(text = stringRes(id = R.string.add)) } } @Composable fun RemoveRelayButton(onClick: () -> Unit) { - Button( + OutlinedButton( modifier = Modifier.padding(horizontal = 3.dp), onClick = onClick, shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.remove), color = Color.White) + Text(text = stringRes(R.string.remove)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index d96d742a1..9b2e44df5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -54,11 +54,11 @@ import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever -import com.vitorpamplona.amethyst.ui.actions.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter import com.vitorpamplona.amethyst.ui.theme.Size15Modifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt index a416c60bb..fd946c474 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/UserCompose.kt @@ -52,7 +52,9 @@ fun UserCompose( UserPicture(baseUser, Size55dp, accountViewModel = accountViewModel, nav = nav) Column(modifier = remember { Modifier.padding(start = 10.dp).weight(1f) }) { - Row(verticalAlignment = Alignment.CenterVertically) { UsernameDisplay(baseUser, accountViewModel = accountViewModel) } + Row(verticalAlignment = Alignment.CenterVertically) { + UsernameDisplay(baseUser, accountViewModel = accountViewModel) + } AboutDisplay(baseUser) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt index 1a9f38ec0..5a04c5e0b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapNoteCompose.kt @@ -34,7 +34,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -165,8 +164,6 @@ fun UserActionOptions( baseAuthor: User, accountViewModel: AccountViewModel, ) { - val scope = rememberCoroutineScope() - WatchIsHiddenUser(baseAuthor, accountViewModel) { isHidden -> if (isHidden) { ShowUserButton { accountViewModel.show(baseAuthor) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt index 8132ccda0..d81c829d5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForDMCard.kt @@ -39,12 +39,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.ui.actions.relays.AddDMRelayListDialog import com.vitorpamplona.amethyst.ui.navigation.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm.AddDMRelayListDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BigPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt index 83cfc46ff..feb746b93 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddInboxRelayForSearchCard.kt @@ -39,12 +39,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.ui.actions.relays.AddSearchRelayListDialog import com.vitorpamplona.amethyst.ui.navigation.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.AddSearchRelayListDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BigPadding import com.vitorpamplona.amethyst.ui.theme.StdPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt index d996c57f6..cd1d15aae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/AddRemoveButtons.kt @@ -24,11 +24,10 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -62,7 +61,7 @@ fun AddButton( modifier: Modifier = Modifier.padding(start = 3.dp), onClick: () -> Unit, ) { - Button( + OutlinedButton( modifier = modifier, onClick = { if (isActive) { @@ -73,7 +72,7 @@ fun AddButton( enabled = isActive, contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp), ) { - Text(text = stringRes(text), color = Color.White, textAlign = TextAlign.Center) + Text(text = stringRes(text), textAlign = TextAlign.Center) } } @@ -82,7 +81,7 @@ fun RemoveButton( isActive: Boolean = true, onClick: () -> Unit, ) { - Button( + OutlinedButton( modifier = Modifier.padding(start = 3.dp), onClick = { if (isActive) { @@ -93,6 +92,6 @@ fun RemoveButton( enabled = isActive, contentPadding = PaddingValues(vertical = 0.dp, horizontal = 16.dp), ) { - Text(text = stringRes(R.string.remove), color = Color.White) + Text(text = stringRes(R.string.remove)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt index 3e5675030..e8a2ee877 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Torrent.kt @@ -52,7 +52,7 @@ import androidx.core.content.ContextCompat import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.actions.relays.countToHumanReadableBytes +import com.vitorpamplona.amethyst.service.countToHumanReadableBytes import com.vitorpamplona.amethyst.ui.components.ShowMoreButton import com.vitorpamplona.amethyst.ui.navigation.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.INav diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt index e7444f8bf..3fd0f6a86 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/TorrentComment.kt @@ -47,7 +47,7 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.actions.relays.countToHumanReadableBytes +import com.vitorpamplona.amethyst.service.countToHumanReadableBytes import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.navigation.EmptyNav diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt index dcc68181f..7ec813330 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NewPostScreen.kt @@ -1698,19 +1698,11 @@ fun CreateButton( modifier: Modifier = Modifier, ) { Button( + enabled = isActive, modifier = modifier, - onClick = { - if (isActive) { - onPost() - } - }, - shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray, - ), + onClick = onPost, ) { - Text(text = stringRes(R.string.create), color = Color.White) + Text(text = stringRes(R.string.create)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChangeChannelMetadataNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChangeChannelMetadataNote.kt index 29aa52549..fa786c9cd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChangeChannelMetadataNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderChangeChannelMetadataNote.kt @@ -22,15 +22,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.ui.Modifier +import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList +import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent @Composable @@ -41,27 +38,19 @@ fun RenderChangeChannelMetadataNote( nav: INav, ) { val noteEvent = note.event as? ChannelMetadataEvent ?: return + val channelInfo = remember(noteEvent) { noteEvent.channelInfo() } + val tags = + remember(noteEvent) { + noteEvent.tags.toImmutableListOfLists() + } - val channelInfo = noteEvent.channelInfo() - val text = - note.author?.toBestDisplayName().toString() + - " ${stringRes(R.string.changed_chat_name_to)} '" + - (channelInfo.name ?: "") + - "', ${stringRes(R.string.description_to)} '" + - (channelInfo.about ?: "") + - "' ${stringRes(R.string.and_picture_to)} " + - (channelInfo.picture ?: "") - - TranslatableRichTextViewer( - content = text, - canPreview = true, - quotesLeft = 0, - modifier = Modifier, - tags = note.author?.info?.tags ?: EmptyTagList, - backgroundColor = bgColor, - id = note.idHex, - callbackUri = note.toNostrUri(), - accountViewModel = accountViewModel, - nav = nav, + RenderChannelData( + noteEvent.id, + note.toNostrUri(), + channelInfo, + tags, + bgColor, + accountViewModel, + nav, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt index ac01d26ac..4cd262587 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/types/RenderCreateChannelNote.kt @@ -20,19 +20,63 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.Nip11CachedRetriever +import com.vitorpamplona.amethyst.service.Nip11Retriever +import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji +import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter +import com.vitorpamplona.amethyst.ui.theme.Size20dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow +import com.vitorpamplona.amethyst.ui.theme.largeProfilePictureModifier +import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList +import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists +import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent +import com.vitorpamplona.quartz.nip28PublicChat.base.ChannelData +import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter @Composable fun RenderCreateChannelNote( @@ -42,27 +86,272 @@ fun RenderCreateChannelNote( nav: INav, ) { val noteEvent = note.event as? ChannelCreateEvent ?: return - val channelInfo = remember { noteEvent.channelInfo() } + val channelInfo = remember(noteEvent) { noteEvent.channelInfo() } + val tags = + remember(noteEvent) { + noteEvent.tags.toImmutableListOfLists() + } - val text = - note.author?.toBestDisplayName().toString() + - " ${stringRes(R.string.created)} " + - (channelInfo.name ?: "") + - " ${stringRes(R.string.with_description_of)} '" + - (channelInfo.about ?: "") + - "' ${stringRes(R.string.and_picture_to)} " + - (channelInfo.picture ?: "") - - TranslatableRichTextViewer( - content = text, - canPreview = true, - quotesLeft = 0, - modifier = Modifier, - tags = note.author?.info?.tags ?: EmptyTagList, - backgroundColor = bgColor, - id = note.idHex, - callbackUri = note.toNostrUri(), - accountViewModel = accountViewModel, - nav = nav, + RenderChannelData( + noteEvent.id, + note.toNostrUri(), + channelInfo, + tags, + bgColor, + accountViewModel, + nav, ) } + +@Preview +@Composable +fun RenderChannelDataPreview() { + ThemeComparisonRow { + RenderChannelData( + id = "bbaacc", + uri = "nostr:nevent1...", + channelInfo = + ChannelData( + "My Group", + "Testing About me", + "http://test.com", + listOf("wss://nostr.mom", "wss://nos.lol"), + ), + tags = EmptyTagList, + bgColor = remember { mutableStateOf(Color.Transparent) }, + accountViewModel = mockAccountViewModel(), + nav = EmptyNav, + ) + } +} + +@Composable +fun RenderChannelData( + id: HexKey, + uri: String, + channelInfo: ChannelData, + tags: ImmutableListOfLists, + bgColor: MutableState, + accountViewModel: AccountViewModel, + nav: INav, +) { + Column { + Row { + TranslatableRichTextViewer( + content = stringRes(R.string.changed_chat_profile_to), + canPreview = true, + quotesLeft = 1, + modifier = Modifier, + tags = tags, + backgroundColor = bgColor, + id = id, + callbackUri = uri, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + channelInfo.picture?.let { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(top = 10.dp), + ) { + RobohashFallbackAsyncImage( + robot = id, + model = it, + contentDescription = stringRes(R.string.channel_image), + modifier = MaterialTheme.colorScheme.largeProfilePictureModifier, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + ) + } + } + + channelInfo.name?.let { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(top = 10.dp), + ) { + CreateTextWithEmoji( + text = it, + tags = tags, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + ) + } + } + + channelInfo.about?.let { + Row( + modifier = Modifier.fillMaxWidth().padding(top = 10.dp), + ) { + TranslatableRichTextViewer( + content = it, + canPreview = true, + quotesLeft = 1, + modifier = Modifier, + tags = tags, + backgroundColor = bgColor, + id = id, + callbackUri = uri, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + + channelInfo.relays?.let { + Text( + stringRes(R.string.public_chat_relays_title) + ": ", + modifier = Modifier.fillMaxWidth().padding(top = 10.dp), + ) + it.forEach { + Spacer(StdVertSpacer) + RenderRelayLinePublicChat( + it, + accountViewModel, + nav, + ) + } + } + } +} + +@Preview +@Composable +fun RenderRelayLinePreview() { + ThemeComparisonRow { + RenderRelayLine( + "wss://nos.lol", + "http://icon.com/icon.ico", + Modifier, + true, + true, + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun RenderRelayLinePublicChat( + dirtyUrl: String, + accountViewModel: AccountViewModel, + nav: INav, +) { + @Suppress("ProduceStateDoesNotAssignValue") + val relayInfo by produceState( + initialValue = Nip11CachedRetriever.getFromCache(dirtyUrl), + ) { + if (value == null) { + accountViewModel.retrieveRelayDocument( + dirtyUrl, + onInfo = { + value = it + }, + onError = { url, errorCode, exceptionMessage -> + }, + ) + } + } + + var openRelayDialog by remember { mutableStateOf(false) } + + val info = + remember(dirtyUrl) { + RelayBriefInfoCache.get(RelayUrlFormatter.normalize(dirtyUrl)) + } + + if (openRelayDialog && relayInfo != null) { + RelayInformationDialog( + onClose = { openRelayDialog = false }, + relayInfo = relayInfo!!, + relayBriefInfo = info, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + val clipboardManager = LocalClipboardManager.current + val clickableModifier = + remember(dirtyUrl) { + Modifier.combinedClickable( + onLongClick = { + clipboardManager.setText(AnnotatedString(dirtyUrl)) + }, + onClick = { + accountViewModel.retrieveRelayDocument( + dirtyUrl, + onInfo = { + openRelayDialog = true + }, + onError = { url, errorCode, exceptionMessage -> + accountViewModel.toastManager.toast( + R.string.unable_to_download_relay_document, + when (errorCode) { + Nip11Retriever.ErrorCode.FAIL_TO_ASSEMBLE_URL -> + R.string.relay_information_document_error_failed_to_assemble_url + + Nip11Retriever.ErrorCode.FAIL_TO_REACH_SERVER -> + R.string.relay_information_document_error_failed_to_reach_server + + Nip11Retriever.ErrorCode.FAIL_TO_PARSE_RESULT -> + R.string.relay_information_document_error_failed_to_parse_response + + Nip11Retriever.ErrorCode.FAIL_WITH_HTTP_STATUS -> + R.string.relay_information_document_error_failed_with_http + }, + url, + exceptionMessage ?: errorCode.toString(), + ) + }, + ) + }, + ) + } + + RenderRelayLine( + info.displayUrl, + relayInfo?.icon ?: info.favIcon, + clickableModifier, + showPicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + ) +} + +@Composable +fun RenderRelayLine( + url: String, + icon: String?, + modifier: Modifier = Modifier, + showPicture: Boolean = true, + loadRobohash: Boolean = true, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = modifier, + ) { + Text(" -") + + Spacer(modifier = StdHorzSpacer) + + RobohashFallbackAsyncImage( + robot = url, + model = icon, + contentDescription = stringRes(id = R.string.relay_info, url), + colorFilter = RelayIconFilter, + modifier = + Modifier + .size(Size20dp) + .clip(shape = CircleShape), + loadProfilePicture = showPicture, + loadRobohash = loadRobohash, + ) + + Spacer(modifier = StdHorzSpacer) + + Text( + text = url, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RenderRoomTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RenderRoomTopBar.kt index 2df854f7b..c6236e5ee 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RenderRoomTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/header/RenderRoomTopBar.kt @@ -31,7 +31,7 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.EditNote -import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -42,7 +42,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -132,7 +131,7 @@ fun GroupMembersHeader( val list = remember(room) { room.users.toPersistentList() } Row( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().padding(5.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically, ) { @@ -181,7 +180,7 @@ private fun EditRoomSubjectButton( NewChatroomSubjectDialog({ wantsToPost = false }, accountViewModel, room) } - Button( + FilledTonalButton( modifier = Modifier .padding(horizontal = 3.dp) @@ -190,7 +189,6 @@ private fun EditRoomSubjectButton( contentPadding = ZeroPadding, ) { Icon( - tint = Color.White, imageVector = Icons.Default.EditNote, contentDescription = stringRes(R.string.edits_the_channel_metadata), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt index 415208e92..1a85cae81 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -62,11 +63,9 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ThinSendButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder -import com.vitorpamplona.amethyst.ui.theme.EditFieldLeadingIconModifier import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier -import com.vitorpamplona.amethyst.ui.theme.Size30Modifier -import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.placeholderText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.collectLatest @@ -76,12 +75,12 @@ import kotlinx.coroutines.launch @Preview @Composable -fun PrivateMessageEditFieldRow() { +fun PrivateMessageEditFieldRowPreview() { val channelScreenModel: ChatNewMessageViewModel = viewModel() val accountViewModel = mockAccountViewModel() channelScreenModel.init(accountViewModel) - ThemeComparisonRow { + ThemeComparisonColumn { PrivateMessageEditFieldRow( channelScreenModel = channelScreenModel, accountViewModel = accountViewModel, @@ -170,12 +169,12 @@ fun PrivateMessageEditFieldRow( leadingIcon = { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.padding(horizontal = 6.dp), + modifier = Modifier.padding(start = 4.dp, end = 10.dp), ) { SelectFromGallery( isUploading = channelScreenModel.isUploadingImage, tint = MaterialTheme.colorScheme.placeholderText, - modifier = EditFieldLeadingIconModifier, + modifier = Modifier, onImageChosen = channelScreenModel::pickedMedia, ) @@ -190,7 +189,7 @@ fun PrivateMessageEditFieldRow( } IconButton( - modifier = Size30Modifier, + modifier = Modifier.width(30.dp), onClick = { if ( !accountViewModel.account.settings.hideNIP17WarningDialog && @@ -208,7 +207,7 @@ fun PrivateMessageEditFieldRow( modifier = Modifier .padding(top = 2.dp) - .size(18.dp), + .size(20.dp), tint = MaterialTheme.colorScheme.primary, ) } else { @@ -216,7 +215,7 @@ fun PrivateMessageEditFieldRow( modifier = Modifier .padding(top = 2.dp) - .size(18.dp), + .size(20.dp), tint = MaterialTheme.colorScheme.placeholderText, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongChannelActionOptions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongChannelActionOptions.kt deleted file mode 100644 index 84a146658..000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongChannelActionOptions.kt +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) 2024 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header - -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.lifecycle.distinctUntilChanged -import androidx.lifecycle.map -import com.vitorpamplona.amethyst.model.PublicChatChannel -import com.vitorpamplona.amethyst.ui.components.LoadNote -import com.vitorpamplona.amethyst.ui.navigation.INav -import com.vitorpamplona.amethyst.ui.note.LikeReaction -import com.vitorpamplona.amethyst.ui.note.ZapReaction -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.EditButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.JoinChatButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.LeaveChatButton -import com.vitorpamplona.amethyst.ui.theme.RowColSpacing -import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer -import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent - -@Composable -fun ShortChannelActionOptions( - channel: PublicChatChannel, - accountViewModel: AccountViewModel, - nav: INav, -) { - LoadNote(baseNoteHex = channel.idHex, accountViewModel) { - it?.let { - Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) { - LikeReaction( - baseNote = it, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav, - ) - ZapReaction( - baseNote = it, - grayTint = MaterialTheme.colorScheme.onSurface, - accountViewModel = accountViewModel, - nav = nav, - ) - Spacer(modifier = StdHorzSpacer) - } - } - } - - WatchChannelFollows(channel, accountViewModel) { isFollowing -> - if (!isFollowing) { - JoinChatButton(channel, accountViewModel, nav) - } - } -} - -@Composable -fun LongChannelActionOptions( - channel: PublicChatChannel, - accountViewModel: AccountViewModel, - nav: INav, -) { - val isMe by - remember(accountViewModel) { - derivedStateOf { channel.creator == accountViewModel.account.userProfile() } - } - - if (isMe) { - EditButton(channel, accountViewModel) - } - - WatchChannelFollows(channel, accountViewModel) { isFollowing -> - if (isFollowing) { - LeaveChatButton(channel, accountViewModel, nav) - } - } -} - -@Composable -private fun WatchChannelFollows( - channel: PublicChatChannel, - accountViewModel: AccountViewModel, - content: @Composable (Boolean) -> Unit, -) { - val isFollowing by - accountViewModel - .userProfile() - .live() - .follows - .map { it.user.latestContactList?.isTaggedEvent(channel.idHex) ?: false } - .distinctUntilChanged() - .observeAsState( - accountViewModel.userProfile().latestContactList?.isTaggedEvent(channel.idHex) ?: false, - ) - - content(isFollowing) -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt index 1bd744c6d..eed1fe260 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongPublicChatChannelHeader.kt @@ -20,25 +20,35 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.map import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.LoadNote +import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture @@ -46,21 +56,64 @@ import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton import com.vitorpamplona.amethyst.ui.note.elements.NormalTimeAgo import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.EditButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.LeaveChatButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.LinkChatButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.OpenChatButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.ShareChatButton import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.Size25dp -import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer +import com.vitorpamplona.amethyst.ui.theme.largeProfilePictureModifier +import com.vitorpamplona.quartz.nip01Core.tags.events.isTaggedEvent @Composable fun LongPublicChatChannelHeader( baseChannel: PublicChatChannel, - lineModifier: Modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp), + lineModifier: Modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 5.dp), accountViewModel: AccountViewModel, nav: INav, ) { val channelState by baseChannel.live.observeAsState() val channel = channelState?.channel as? PublicChatChannel ?: return + Spacer(StdVertSpacer) + + channel.info.picture?.let { + Row( + horizontalArrangement = Arrangement.Center, + modifier = lineModifier, + ) { + RobohashFallbackAsyncImage( + robot = baseChannel.idHex, + model = it, + contentDescription = stringRes(R.string.channel_image), + modifier = MaterialTheme.colorScheme.largeProfilePictureModifier, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + ) + } + } + + channel.info.name?.let { + Row( + horizontalArrangement = Arrangement.Center, + modifier = lineModifier, + ) { + CreateTextWithEmoji( + text = it, + tags = channel.infoTags, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + ) + } + } + + Row(horizontalArrangement = Arrangement.Center, modifier = lineModifier) { + LongChannelActionOptions(channel, accountViewModel, nav) + } + Row(lineModifier) { val summary = remember(channelState) { channel.summary()?.ifBlank { null } } @@ -73,7 +126,7 @@ fun LongPublicChatChannelHeader( content = summary ?: stringRes(id = R.string.groups_no_descriptor), canPreview = false, quotesLeft = 1, - tags = EmptyTagList, + tags = channel.infoTags, backgroundColor = background, id = baseChannel.idHex, accountViewModel = accountViewModel, @@ -81,9 +134,6 @@ fun LongPublicChatChannelHeader( ) } } - - Spacer(DoubleHorzSpacer) - LongChannelActionOptions(channel, accountViewModel, nav) } LoadNote(baseNoteHex = channel.idHex, accountViewModel) { loadingNote -> @@ -120,4 +170,54 @@ fun LongPublicChatChannelHeader( } } } + + Spacer(StdVertSpacer) +} + +@Composable +fun LongChannelActionOptions( + channel: PublicChatChannel, + accountViewModel: AccountViewModel, + nav: INav, +) { + val isMe by + remember(accountViewModel) { + derivedStateOf { channel.creator == accountViewModel.account.userProfile() } + } + + OpenChatButton(channel, accountViewModel, nav) + + LinkChatButton(channel, accountViewModel, nav) + + ShareChatButton(channel, accountViewModel, nav) + + if (isMe) { + EditButton(channel, accountViewModel, nav) + } + + WatchChannelFollows(channel, accountViewModel) { isFollowing -> + if (isFollowing) { + LeaveChatButton(channel, accountViewModel, nav) + } + } +} + +@Composable +fun WatchChannelFollows( + channel: PublicChatChannel, + accountViewModel: AccountViewModel, + content: @Composable (Boolean) -> Unit, +) { + val isFollowing by + accountViewModel + .userProfile() + .live() + .follows + .map { it.user.latestContactList?.isTaggedEvent(channel.idHex) ?: false } + .distinctUntilChanged() + .observeAsState( + accountViewModel.userProfile().latestContactList?.isTaggedEvent(channel.idHex) ?: false, + ) + + content(isFollowing) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/PublicChatTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/PublicChatTopBar.kt index a4a42d252..2f034d451 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/PublicChatTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/PublicChatTopBar.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28 import androidx.compose.runtime.Composable import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.popBack import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.TopBarExtensibleWithBackButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt index f7da4d439..ea1461986 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/ShortPublicChatChannelHeader.kt @@ -23,8 +23,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28 import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -38,12 +40,18 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.LikeReaction +import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions.JoinChatButton import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier +import com.vitorpamplona.amethyst.ui.theme.RowColSpacing import com.vitorpamplona.amethyst.ui.theme.Size35dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer @Composable fun ShortPublicChatChannelHeader( @@ -94,3 +102,36 @@ fun ShortPublicChatChannelHeader( } } } + +@Composable +fun ShortChannelActionOptions( + channel: PublicChatChannel, + accountViewModel: AccountViewModel, + nav: INav, +) { + LoadNote(baseNoteHex = channel.idHex, accountViewModel) { + it?.let { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) { + LikeReaction( + baseNote = it, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav, + ) + ZapReaction( + baseNote = it, + grayTint = MaterialTheme.colorScheme.onSurface, + accountViewModel = accountViewModel, + nav = nav, + ) + Spacer(modifier = StdHorzSpacer) + } + } + } + + WatchChannelFollows(channel, accountViewModel) { isFollowing -> + if (!isFollowing) { + JoinChatButton(channel, accountViewModel, nav) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/EditChatButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/EditChatButton.kt index 2897570b8..a1e50c8b2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/EditChatButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/EditChatButton.kt @@ -24,20 +24,18 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.EditNote -import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav +import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ZeroPadding @@ -45,23 +43,17 @@ import com.vitorpamplona.amethyst.ui.theme.ZeroPadding fun EditButton( channel: PublicChatChannel, accountViewModel: AccountViewModel, + nav: INav, ) { - var wantsToPost by remember { mutableStateOf(false) } - - if (wantsToPost) { - ChannelMetadataDialog({ wantsToPost = false }, accountViewModel, channel) - } - - Button( + FilledTonalButton( modifier = Modifier .padding(horizontal = 3.dp) .width(50.dp), - onClick = { wantsToPost = true }, + onClick = { nav.nav("ChannelMetadataEdit?id=${channel.idHex}") }, contentPadding = ZeroPadding, ) { Icon( - tint = Color.White, imageVector = Icons.Default.EditNote, contentDescription = stringRes(R.string.edits_the_channel_metadata), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/JoinChatButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/JoinChatButton.kt index 35dbf3965..9d6af6e7e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/JoinChatButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/JoinChatButton.kt @@ -20,10 +20,9 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions -import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.PublicChatChannel import com.vitorpamplona.amethyst.ui.navigation.INav @@ -38,11 +37,11 @@ fun JoinChatButton( accountViewModel: AccountViewModel, nav: INav, ) { - Button( + FilledTonalButton( modifier = HalfHalfHorzModifier, onClick = { accountViewModel.follow(channel) }, contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.join), color = Color.White) + Text(text = stringRes(R.string.join)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LeaveChatButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LeaveChatButton.kt index c91f1c045..0468dae83 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LeaveChatButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LeaveChatButton.kt @@ -20,10 +20,9 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions -import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.PublicChatChannel import com.vitorpamplona.amethyst.ui.navigation.INav @@ -38,11 +37,11 @@ fun LeaveChatButton( accountViewModel: AccountViewModel, nav: INav, ) { - Button( + FilledTonalButton( modifier = HalfHalfHorzModifier, onClick = { accountViewModel.unfollow(channel) }, contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.leave), color = Color.White) + Text(text = stringRes(R.string.leave)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LinkChatButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LinkChatButton.kt new file mode 100644 index 000000000..7d9949609 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LinkChatButton.kt @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions + +import android.content.Intent +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ContentCopy +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size20Modifier +import com.vitorpamplona.amethyst.ui.theme.ZeroPadding + +@Composable +fun LinkChatButton( + channel: PublicChatChannel, + accountViewModel: AccountViewModel, + nav: INav, +) { + val context = LocalContext.current + + FilledTonalButton( + modifier = + Modifier + .padding(horizontal = 3.dp) + .width(50.dp), + onClick = { + val intent = Intent(Intent.ACTION_VIEW, channel.toNostrUri().toUri()) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + + ContextCompat.startActivity(context, intent, null) + + val sendIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, channel.toNostrUri()) + putExtra(Intent.EXTRA_TITLE, stringRes(context, R.string.quick_action_share_browser_link)) + } + + val shareIntent = + Intent.createChooser( + sendIntent, + stringRes(context, R.string.quick_action_copy_note_id), + ) + + ContextCompat.startActivity(context, shareIntent, null) + }, + contentPadding = ZeroPadding, + ) { + Icon( + imageVector = Icons.Default.ContentCopy, + contentDescription = stringRes(R.string.quick_action_copy_note_id), + modifier = Size20Modifier, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/OpenChatButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/OpenChatButton.kt new file mode 100644 index 000000000..01cf7c51c --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/OpenChatButton.kt @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions + +import android.content.Intent +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.OpenInNew +import androidx.compose.material.icons.filled.OpenInNew +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size20Modifier +import com.vitorpamplona.amethyst.ui.theme.ZeroPadding + +@Composable +fun OpenChatButton( + channel: PublicChatChannel, + accountViewModel: AccountViewModel, + nav: INav, +) { + val context = LocalContext.current + + FilledTonalButton( + modifier = + Modifier + .padding(horizontal = 3.dp) + .width(50.dp), + onClick = { + val intent = Intent(Intent.ACTION_VIEW, channel.toNostrUri().toUri()) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + + ContextCompat.startActivity(context, intent, null) + }, + contentPadding = ZeroPadding, + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.OpenInNew, + contentDescription = stringRes(R.string.quick_actions_open_in_another_app), + modifier = Size20Modifier, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/ShareChatButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/ShareChatButton.kt new file mode 100644 index 000000000..a86daef86 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/ShareChatButton.kt @@ -0,0 +1,84 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header.actions + +import android.content.Intent +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.note.njumpLink +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size20Modifier +import com.vitorpamplona.amethyst.ui.theme.ZeroPadding + +@Composable +fun ShareChatButton( + channel: PublicChatChannel, + accountViewModel: AccountViewModel, + nav: INav, +) { + val context = LocalContext.current + + FilledTonalButton( + modifier = + Modifier + .padding(horizontal = 3.dp) + .width(50.dp), + onClick = { + val sendIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, njumpLink(channel.toNEvent())) + putExtra(Intent.EXTRA_TITLE, stringRes(context, R.string.quick_action_share_browser_link)) + } + + val shareIntent = + Intent.createChooser( + sendIntent, + stringRes(context, R.string.quick_action_share), + ) + + ContextCompat.startActivity(context, shareIntent, null) + }, + contentPadding = ZeroPadding, + ) { + Icon( + imageVector = Icons.Default.Share, + contentDescription = stringRes(R.string.quick_action_share), + modifier = Size20Modifier, + ) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataDialog.kt deleted file mode 100644 index 3cc03879f..000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataDialog.kt +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Copyright (c) 2024 Vitor Pamplona - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the - * Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN - * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.LocalTextStyle -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.input.KeyboardCapitalization -import androidx.compose.ui.text.style.TextDirection -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties -import androidx.lifecycle.viewmodel.compose.viewModel -import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.PublicChatChannel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.PostButton -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.placeholderText - -@Composable -fun ChannelMetadataDialog( - onClose: () -> Unit, - accountViewModel: AccountViewModel, - channel: PublicChatChannel? = null, -) { - val postViewModel: ChannelMetadataViewModel = viewModel() - postViewModel.load(accountViewModel.account, channel) - - Dialog( - onDismissRequest = { onClose() }, - properties = - DialogProperties( - dismissOnClickOutside = false, - ), - ) { - DialogContent(postViewModel, onClose) - } -} - -@Composable -private fun DialogContent( - postViewModel: ChannelMetadataViewModel, - onClose: () -> Unit, -) { - Surface { - Column( - modifier = - Modifier - .padding(10.dp) - .verticalScroll(rememberScrollState()), - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - CloseButton( - onPress = { - postViewModel.clear() - onClose() - }, - ) - - PostButton( - onPost = { - postViewModel.create() - onClose() - }, - postViewModel.channelName.value.text - .isNotBlank(), - ) - } - - Spacer(modifier = Modifier.height(15.dp)) - - OutlinedTextField( - label = { Text(text = stringRes(R.string.channel_name)) }, - modifier = Modifier.fillMaxWidth(), - value = postViewModel.channelName.value, - onValueChange = { postViewModel.channelName.value = it }, - placeholder = { - Text( - text = stringRes(R.string.my_awesome_group), - color = MaterialTheme.colorScheme.placeholderText, - ) - }, - keyboardOptions = - KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.Sentences, - ), - textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), - ) - - Spacer(modifier = Modifier.height(15.dp)) - - OutlinedTextField( - label = { Text(text = stringRes(R.string.picture_url)) }, - modifier = Modifier.fillMaxWidth(), - value = postViewModel.channelPicture.value, - onValueChange = { postViewModel.channelPicture.value = it }, - placeholder = { - Text( - text = "http://mygroup.com/logo.jpg", - color = MaterialTheme.colorScheme.placeholderText, - ) - }, - ) - - Spacer(modifier = Modifier.height(15.dp)) - - OutlinedTextField( - label = { Text(text = stringRes(R.string.description)) }, - modifier = - Modifier - .fillMaxWidth() - .height(100.dp), - value = postViewModel.channelDescription.value, - onValueChange = { postViewModel.channelDescription.value = it }, - placeholder = { - Text( - text = stringRes(R.string.about_us), - color = MaterialTheme.colorScheme.placeholderText, - ) - }, - keyboardOptions = - KeyboardOptions.Default.copy( - capitalization = KeyboardCapitalization.Sentences, - ), - textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), - maxLines = 10, - ) - } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt new file mode 100644 index 000000000..715f8b1af --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt @@ -0,0 +1,318 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata + +import androidx.compose.foundation.layout.Arrangement +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.imePadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDirection +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav +import com.vitorpamplona.amethyst.ui.navigation.INav +import com.vitorpamplona.amethyst.ui.navigation.routeFor +import com.vitorpamplona.amethyst.ui.note.LoadChannel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.CreateButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer +import com.vitorpamplona.amethyst.ui.theme.MinHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn +import com.vitorpamplona.amethyst.ui.theme.placeholderText +import com.vitorpamplona.quartz.nip01Core.core.HexKey + +@Composable +fun ChannelMetadataScreen( + channelId: HexKey? = null, + accountViewModel: AccountViewModel, + nav: INav, +) { + if (channelId == null) { + ChannelMetadataScreen(null as PublicChatChannel?, accountViewModel, nav) + } else { + LoadChannel(channelId, accountViewModel) { + if (it is PublicChatChannel) { + ChannelMetadataScreen(it, accountViewModel, nav) + } + } + } +} + +@Composable +fun ChannelMetadataScreen( + channel: PublicChatChannel? = null, + accountViewModel: AccountViewModel, + nav: INav, +) { + val postViewModel: ChannelMetadataViewModel = viewModel() + postViewModel.load(accountViewModel.account, channel) + + ChannelMetadataScaffold( + postViewModel = postViewModel, + accountViewModel = accountViewModel, + nav = nav, + ) +} + +@Preview +@Composable +private fun DialogContentPreview() { + val accountViewModel = mockAccountViewModel() + val postViewModel: ChannelMetadataViewModel = viewModel() + postViewModel.load(accountViewModel.account, null) + + ThemeComparisonColumn { + ChannelMetadataScaffold( + postViewModel = postViewModel, + accountViewModel = accountViewModel, + nav = EmptyNav, + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ChannelMetadataScaffold( + postViewModel: ChannelMetadataViewModel, + accountViewModel: AccountViewModel, + nav: INav, +) { + Scaffold( + topBar = { + TopAppBar( + title = { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Spacer(modifier = MinHorzSpacer) + + Text( + text = stringRes(R.string.public_chat), + modifier = Modifier.weight(1f), + textAlign = TextAlign.Center, + style = MaterialTheme.typography.titleLarge, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + ) + + if (postViewModel.isNewChannel()) { + CreateButton( + onPost = { + postViewModel.createOrUpdate { + nav.nav(routeFor(it)) + } + nav.popBack() + }, + postViewModel.canPost, + ) + } else { + SaveButton( + onPost = { + postViewModel.createOrUpdate { } + nav.popBack() + }, + postViewModel.canPost, + ) + } + } + }, + navigationIcon = { + Row { + Spacer(modifier = StdHorzSpacer) + CloseButton( + onPress = { + postViewModel.clear() + nav.popBack() + }, + ) + } + }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + ) + }, + ) { pad -> + val feedState by postViewModel.channelRelays.collectAsStateWithLifecycle() + + LazyColumn( + Modifier + .fillMaxSize() + .padding( + start = 10.dp, + end = 10.dp, + top = pad.calculateTopPadding(), + bottom = pad.calculateBottomPadding(), + ).consumeWindowInsets(pad) + .imePadding(), + ) { + item { + SettingsCategory( + stringRes(R.string.public_chat_title), + stringRes(R.string.public_chat_explainer), + Modifier.padding(bottom = 8.dp), + ) + + ChannelName(postViewModel) + + Spacer(modifier = DoubleVertSpacer) + + Picture(postViewModel, accountViewModel) + + Spacer(modifier = DoubleVertSpacer) + + Description(postViewModel) + + SettingsCategory( + stringRes(R.string.public_chat_relays_title), + stringRes(R.string.public_chat_relays_explainer), + ) + } + + itemsIndexed(feedState, key = { _, item -> "ChatRelays" + item.url }) { index, item -> + BasicRelaySetupInfoDialog( + item, + onDelete = { postViewModel.deleteHomeRelay(item) }, + accountViewModel = accountViewModel, + nav, + ) + } + + item { + RelayUrlEditField { postViewModel.addHomeRelay(relaySetupInfoBuilder(it)) } + + Spacer(modifier = DoubleVertSpacer) + } + } + } +} + +@Composable +private fun Description(postViewModel: ChannelMetadataViewModel) { + OutlinedTextField( + label = { Text(text = stringRes(R.string.description)) }, + modifier = Modifier.fillMaxWidth(), + value = postViewModel.channelDescription.value, + onValueChange = { postViewModel.channelDescription.value = it }, + placeholder = { + Text( + text = stringRes(R.string.about_us), + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + keyboardOptions = + KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences, + ), + textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), + minLines = 3, + ) +} + +@Composable +private fun Picture( + postViewModel: ChannelMetadataViewModel, + accountViewModel: AccountViewModel, +) { + OutlinedTextField( + label = { Text(text = stringRes(R.string.picture_url)) }, + modifier = Modifier.fillMaxWidth(), + value = postViewModel.channelPicture.value, + onValueChange = { postViewModel.channelPicture.value = it }, + placeholder = { + Text( + text = "http://mygroup.com/logo.jpg", + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + leadingIcon = { + val context = LocalContext.current + SelectSingleFromGallery( + isUploading = postViewModel.isUploadingImageForPicture, + tint = MaterialTheme.colorScheme.placeholderText, + modifier = Modifier.padding(start = 2.dp), + ) { + postViewModel.uploadForPicture(it, context, onError = accountViewModel.toastManager::toast) + } + }, + ) +} + +@Composable +private fun ChannelName(postViewModel: ChannelMetadataViewModel) { + OutlinedTextField( + label = { Text(text = stringRes(R.string.channel_name)) }, + modifier = Modifier.fillMaxWidth(), + value = postViewModel.channelName.value, + onValueChange = { postViewModel.channelName.value = it }, + placeholder = { + Text( + text = stringRes(R.string.my_awesome_group), + color = MaterialTheme.colorScheme.placeholderText, + ) + }, + keyboardOptions = + KeyboardOptions.Default.copy( + capitalization = KeyboardCapitalization.Sentences, + ), + textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content), + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt index b16d9317c..85993cecd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt @@ -20,18 +20,40 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata +import android.content.Context +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.PublicChatChannel +import com.vitorpamplona.amethyst.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.service.uploads.MediaCompressor +import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader +import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader +import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType +import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.tags.events.ETag import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlin.collections.isNotEmpty +import kotlin.collections.map +import kotlin.collections.plus +import kotlin.coroutines.cancellation.CancellationException class ChannelMetadataViewModel : ViewModel() { private var account: Account? = null @@ -41,6 +63,15 @@ class ChannelMetadataViewModel : ViewModel() { val channelPicture = mutableStateOf(TextFieldValue()) val channelDescription = mutableStateOf(TextFieldValue()) + private val _channelRelays = MutableStateFlow>(emptyList()) + val channelRelays = _channelRelays.asStateFlow() + + var isUploadingImageForPicture by mutableStateOf(false) + + val canPost by derivedStateOf { + channelName.value.text.isNotBlank() + } + fun load( account: Account, channel: PublicChatChannel?, @@ -51,10 +82,19 @@ class ChannelMetadataViewModel : ViewModel() { channelName.value = TextFieldValue(channel.info.name ?: "") channelPicture.value = TextFieldValue(channel.info.picture ?: "") channelDescription.value = TextFieldValue(channel.info.about ?: "") + + val relays = + channel.info.relays + ?.map { relaySetupInfoBuilder(it) } + ?.distinctBy { it.url } + + _channelRelays.update { relays ?: emptyList() } } } - fun create() { + fun isNewChannel() = originalChannel == null && _channelRelays.value.isNotEmpty() + + fun createOrUpdate(onDone: (PublicChatChannel) -> Unit) { viewModelScope.launch(Dispatchers.IO) { account?.let { account -> val channel = originalChannel @@ -64,10 +104,21 @@ class ChannelMetadataViewModel : ViewModel() { channelName.value.text, channelDescription.value.text, channelPicture.value.text, - null, + channelRelays.value.map { it.url }, ) - account.sendCreateNewChannel(template) + account.signAndSendPrivatelyOrBroadcast( + template, + relayList = { it.channelInfo().relays }, + onDone = { + val channel = LocalCache.getOrCreateChannel(it.id) { PublicChatChannel(it) } + // follows the channel + account.follow(channel) + if (channel is PublicChatChannel) { + onDone(channel) + } + }, + ) } else { val event = channel.event @@ -79,7 +130,7 @@ class ChannelMetadataViewModel : ViewModel() { channelName.value.text, channelDescription.value.text, channelPicture.value.text, - null, + channelRelays.value.map { it.url }, hint, ) } else { @@ -89,12 +140,21 @@ class ChannelMetadataViewModel : ViewModel() { channelName.value.text, channelDescription.value.text, channelPicture.value.text, - null, + channelRelays.value.map { it.url }, eTag, ) } - account.sendChangeChannel(template) + account.signAndSendPrivatelyOrBroadcast( + template, + relayList = { it.channelInfo().relays }, + onDone = { + val channel = LocalCache.getOrCreateChannel(it.id) { PublicChatChannel(it) } + if (channel is PublicChatChannel) { + onDone(channel) + } + }, + ) } } @@ -102,9 +162,91 @@ class ChannelMetadataViewModel : ViewModel() { } } + fun addHomeRelay(relay: BasicRelaySetupInfo) { + if (_channelRelays.value.any { it.url == relay.url }) return + + _channelRelays.update { it.plus(relay) } + } + + fun deleteHomeRelay(relay: BasicRelaySetupInfo) { + _channelRelays.update { it.minus(relay) } + } + fun clear() { channelName.value = TextFieldValue() channelPicture.value = TextFieldValue() channelDescription.value = TextFieldValue() + _channelRelays.update { emptyList() } + } + + fun uploadForPicture( + uri: SelectedMedia, + context: Context, + onError: (String, String) -> Unit, + ) { + viewModelScope.launch(Dispatchers.IO) { + upload( + uri, + context, + onUploading = { isUploadingImageForPicture = it }, + onUploaded = { channelPicture.value = TextFieldValue(it) }, + onError = onError, + ) + } + } + + private suspend fun upload( + galleryUri: SelectedMedia, + context: Context, + onUploading: (Boolean) -> Unit, + onUploaded: (String) -> Unit, + onError: (String, String) -> Unit, + ) { + val account = account ?: return + onUploading(true) + + val compResult = MediaCompressor().compress(galleryUri.uri, galleryUri.mimeType, CompressorQuality.MEDIUM, context.applicationContext) + + try { + val result = + if (account.settings.defaultFileServer.type == ServerType.NIP96) { + Nip96Uploader().upload( + uri = compResult.uri, + contentType = compResult.contentType, + size = compResult.size, + alt = null, + sensitiveContent = null, + serverBaseUrl = account.settings.defaultFileServer.baseUrl, + forceProxy = account::shouldUseTorForNIP96, + onProgress = {}, + httpAuth = account::createHTTPAuthorization, + context = context, + ) + } else { + BlossomUploader().upload( + uri = compResult.uri, + contentType = compResult.contentType, + size = compResult.size, + alt = null, + sensitiveContent = null, + serverBaseUrl = account.settings.defaultFileServer.baseUrl, + forceProxy = account::shouldUseTorForNIP96, + httpAuth = account::createBlossomUploadAuth, + context = context, + ) + } + + if (result.url != null) { + onUploading(false) + onUploaded(result.url) + } else { + onUploading(false) + onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, R.string.server_did_not_provide_a_url_after_uploading)) + } + } catch (e: Exception) { + if (e is CancellationException) throw e + onUploading(false) + onError(stringRes(context, R.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt index 96f6dfaa8..f80c62720 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -33,6 +35,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextDirection +import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery @@ -45,7 +48,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.DisplayReplying import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ThinSendButton import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder -import com.vitorpamplona.amethyst.ui.theme.EditFieldLeadingIconModifier import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier import com.vitorpamplona.amethyst.ui.theme.placeholderText @@ -139,7 +141,7 @@ fun EditFieldRow( SelectFromGallery( isUploading = channelScreenModel.isUploadingImage, tint = MaterialTheme.colorScheme.placeholderText, - modifier = EditFieldLeadingIconModifier, + modifier = Modifier.height(32.dp).padding(start = 2.dp), onImageChosen = channelScreenModel::pickedMedia, ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChannelFabColumn.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChannelFabColumn.kt index e2984d219..d3cfb9a4e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChannelFabColumn.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChannelFabColumn.kt @@ -48,27 +48,17 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.navigation.EmptyNav.nav import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.buildNewPostRoute -import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font12SP import com.vitorpamplona.amethyst.ui.theme.Size55Modifier @Composable -fun ChannelFabColumn( - accountViewModel: AccountViewModel, - nav: INav, -) { +fun ChannelFabColumn(nav: INav) { var isOpen by remember { mutableStateOf(false) } - var wantsToCreateChannel by remember { mutableStateOf(false) } - - if (wantsToCreateChannel) { - ChannelMetadataDialog({ wantsToCreateChannel = false }, accountViewModel = accountViewModel) - } - Column { AnimatedVisibility( visible = isOpen, @@ -101,7 +91,7 @@ fun ChannelFabColumn( FloatingActionButton( onClick = { - wantsToCreateChannel = true + nav.nav("ChannelMetadataEdit") isOpen = false }, modifier = Size55Modifier, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/singlepane/MessagesSinglePane.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/singlepane/MessagesSinglePane.kt index df423756d..3d64f1e61 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/singlepane/MessagesSinglePane.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/singlepane/MessagesSinglePane.kt @@ -90,7 +90,7 @@ fun MessagesSinglePane( } }, floatingButton = { - ChannelFabColumn(accountViewModel, nav) + ChannelFabColumn(nav) }, accountViewModel = accountViewModel, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/twopane/MessagesTwoPane.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/twopane/MessagesTwoPane.kt index 2d88c2115..5902df4c2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/twopane/MessagesTwoPane.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/twopane/MessagesTwoPane.kt @@ -97,7 +97,7 @@ fun MessagesTwoPane( ) Box(Modifier.padding(Size20dp), contentAlignment = Alignment.Center) { - ChannelFabColumn(accountViewModel, nav) + ChannelFabColumn(nav) } } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/FollowButtons.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/FollowButtons.kt index 4f3d73f39..4416a934c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/FollowButtons.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/FollowButtons.kt @@ -21,13 +21,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R @@ -40,32 +37,24 @@ fun FollowButton( text: Int = R.string.follow, onClick: () -> Unit, ) { - Button( + FilledTonalButton( modifier = Modifier.padding(start = 3.dp), onClick = onClick, shape = LeftHalfCircleButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(text), color = Color.White, textAlign = TextAlign.Center) + Text(text = stringRes(text), textAlign = TextAlign.Center) } } @Composable fun UnfollowButton(onClick: () -> Unit) { - Button( + FilledTonalButton( modifier = Modifier.padding(horizontal = 3.dp), onClick = onClick, shape = LeftHalfCircleButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.unfollow), color = Color.White) + Text(text = stringRes(R.string.unfollow)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/EditButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/EditButton.kt index ab162e830..c14d0d78d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/EditButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/EditButton.kt @@ -24,11 +24,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.EditNote -import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R @@ -50,7 +49,7 @@ fun InnerEditButtonPreview() { @Composable fun InnerEditButton(onClick: () -> Unit) { - Button( + FilledTonalButton( modifier = Modifier .padding(horizontal = 3.dp) @@ -59,7 +58,6 @@ fun InnerEditButton(onClick: () -> Unit) { contentPadding = ZeroPadding, ) { Icon( - tint = Color.White, imageVector = Icons.Default.EditNote, contentDescription = stringRes(R.string.edits_the_user_s_metadata), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/MessageButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/MessageButton.kt index 705575cdd..36a32d006 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/MessageButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/MessageButton.kt @@ -22,12 +22,11 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.material3.Button +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R @@ -48,7 +47,7 @@ fun MessageButton( ) { val scope = rememberCoroutineScope() - Button( + FilledTonalButton( modifier = Modifier .padding(horizontal = 3.dp) @@ -62,7 +61,6 @@ fun MessageButton( painter = painterResource(R.drawable.ic_dm), stringRes(R.string.send_a_direct_message), modifier = Size20Modifier, - tint = Color.White, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ShowUserButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ShowUserButton.kt index 7def995e8..45d5c3512 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ShowUserButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ShowUserButton.kt @@ -21,13 +21,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.stringRes @@ -36,7 +35,7 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonPadding @Composable fun ShowUserButton(onClick: () -> Unit) { - Button( + FilledTonalButton( modifier = Modifier.padding(start = 3.dp), onClick = onClick, shape = ButtonBorder, @@ -46,6 +45,6 @@ fun ShowUserButton(onClick: () -> Unit) { ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.unblock), color = Color.White) + Text(text = stringRes(R.string.unblock)) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt index 7d360ad6f..de685a6e8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt @@ -20,22 +20,26 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column 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.width -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -43,7 +47,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -57,6 +60,7 @@ import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.DisplayNIP05 import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage +import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -65,6 +69,8 @@ import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font14SP import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size35dp +import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer +import com.vitorpamplona.amethyst.ui.theme.largeProfilePictureModifier import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata @Preview @@ -98,6 +104,7 @@ fun BackButton(onPress: () -> Unit) { } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ShowQRDialog( user: User, @@ -111,96 +118,113 @@ fun ShowQRDialog( onDismissRequest = onClose, properties = DialogProperties(usePlatformDefaultWidth = false), ) { - Surface { - Column { - Row( - modifier = Modifier.padding(10.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - BackButton(onPress = onClose) - } + SetDialogToEdgeToEdge() - Column( - modifier = Modifier.fillMaxSize().padding(horizontal = 10.dp), - verticalArrangement = Arrangement.SpaceAround, - ) { - if (presenting) { - Column { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - RobohashFallbackAsyncImage( - robot = user.pubkeyHex, - model = user.profilePicture(), - contentDescription = stringRes(R.string.profile_image), - modifier = - Modifier - .width(120.dp) - .height(120.dp) - .clip(shape = CircleShape) - .border(3.dp, MaterialTheme.colorScheme.onBackground, CircleShape), - loadProfilePicture = accountViewModel.settings.showProfilePictures.value, - loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, - ) - } - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth().padding(top = 10.dp), - ) { - CreateTextWithEmoji( - text = user.info?.bestName() ?: "", - tags = user.info?.tags, - fontWeight = FontWeight.Bold, - fontSize = 20.sp, - ) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth().padding(top = 4.dp), - ) { - val nip05 = user.nip05() - if (nip05 != null) { - val nip05Verified = - nip05VerificationAsAState(user.info!!, user.pubkeyHex, accountViewModel) - - DisplayNIP05(nip05, nip05Verified, accountViewModel) - } else { - Text( - text = user.pubkeyDisplayHex(), - fontSize = Font14SP, - maxLines = 1, - overflow = TextOverflow.Ellipsis, + Scaffold( + topBar = { + TopAppBar( + title = {}, + navigationIcon = { + Row { + Spacer(modifier = StdHorzSpacer) + BackButton(onPress = onClose) + } + }, + colors = + TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + ), + ) + }, + ) { pad -> + Surface( + Modifier + .fillMaxSize() + .padding( + start = 10.dp, + end = 10.dp, + top = pad.calculateTopPadding(), + bottom = pad.calculateBottomPadding(), + ).consumeWindowInsets(pad) + .imePadding(), + ) { + Column { + Column( + modifier = Modifier.fillMaxSize().padding(horizontal = 10.dp), + verticalArrangement = Arrangement.SpaceAround, + ) { + if (presenting) { + Column { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + RobohashFallbackAsyncImage( + robot = user.pubkeyHex, + model = user.profilePicture(), + contentDescription = stringRes(R.string.profile_image), + modifier = MaterialTheme.colorScheme.largeProfilePictureModifier, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) } + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(top = 10.dp), + ) { + CreateTextWithEmoji( + text = user.info?.bestName() ?: "", + tags = user.info?.tags, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(top = 4.dp), + ) { + val nip05 = user.nip05() + if (nip05 != null) { + val nip05Verified = + nip05VerificationAsAState(user.info!!, user.pubkeyHex, accountViewModel) + + DisplayNIP05(nip05, nip05Verified, accountViewModel) + } else { + Text( + text = user.pubkeyDisplayHex(), + fontSize = Font14SP, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } } - } - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp), - ) { - QrCodeDrawer(user.toNostrUri()) - } - - Row(modifier = Modifier.padding(horizontal = 30.dp)) { - FilledTonalButton( - onClick = { presenting = false }, - shape = RoundedCornerShape(Size35dp), - modifier = Modifier.fillMaxWidth().height(50.dp), + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth().padding(horizontal = Size10dp), ) { - Text(text = stringRes(R.string.scan_qr)) + QrCodeDrawer(user.toNostrUri()) } - } - } else { - NIP19QrCodeScanner { - if (it.isNullOrEmpty()) { - presenting = true - } else { - onScan(it) + + Row(modifier = Modifier.padding(horizontal = 30.dp)) { + FilledTonalButton( + onClick = { presenting = false }, + shape = RoundedCornerShape(Size35dp), + modifier = Modifier.fillMaxWidth().height(50.dp), + ) { + Text(text = stringRes(R.string.scan_qr)) + } + } + } else { + NIP19QrCodeScanner { + if (it.isNullOrEmpty()) { + presenting = true + } else { + onScan(it) + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt similarity index 88% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt index 24549b416..e52a465db 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -54,6 +54,21 @@ import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm.DMRelayListViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm.renderDMItems +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kind3.Kind3RelayListViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kind3.renderKind3Items +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kind3.renderKind3ProposalItems +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.local.LocalRelayListViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.local.renderLocalItems +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip37.PrivateOutboxRelayListViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip37.renderPrivateOutboxItems +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.Nip65RelayListViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.renderNip65HomeItems +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.renderNip65NotifItems +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayListViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.renderSearchItems import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.MinHorzSpacer @@ -61,10 +76,9 @@ import com.vitorpamplona.amethyst.ui.theme.RowColSpacing import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.grayText import com.vitorpamplona.ammolite.relays.Constants -import com.vitorpamplona.quartz.nip01Core.relay.RelayStat @Composable -fun AllRelayListView( +fun AllRelayListScreen( relayToAdd: String? = null, accountViewModel: AccountViewModel, nav: INav, @@ -275,7 +289,11 @@ fun ResetSearchRelays(postViewModel: SearchRelayListViewModel) { OutlinedButton( onClick = { postViewModel.deleteAll() - DefaultSearchRelayList.forEach { postViewModel.addRelay(BasicRelaySetupInfo(it, RelayStat())) } + DefaultSearchRelayList.forEach { + postViewModel.addRelay( + relaySetupInfoBuilder(it), + ) + } postViewModel.loadRelayDocuments() }, ) { @@ -288,7 +306,9 @@ fun ResetDMRelays(postViewModel: DMRelayListViewModel) { OutlinedButton( onClick = { postViewModel.deleteAll() - DefaultDMRelayList.forEach { postViewModel.addRelay(BasicRelaySetupInfo(it, RelayStat())) } + DefaultDMRelayList.forEach { + postViewModel.addRelay(relaySetupInfoBuilder(it)) + } postViewModel.loadRelayDocuments() }, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationDialog.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationDialog.kt index eeb077170..3fb474081 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationDialog.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt new file mode 100644 index 000000000..7d29082f2 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache +import com.vitorpamplona.ammolite.relays.RelayStats +import com.vitorpamplona.quartz.nip01Core.relay.RelayStat +import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter + +@Immutable +data class BasicRelaySetupInfo( + val url: String, + val relayStat: RelayStat, + val paidRelay: Boolean = false, +) { + val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url) +} + +fun relaySetupInfoBuilder(url: String): BasicRelaySetupInfo { + val normalized = RelayUrlFormatter.normalize(url) + return BasicRelaySetupInfo( + normalized, + RelayStats.get(normalized), + ) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt index a13218212..d0fee4c97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoClickableRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt index 4cc08ed1c..66b55a623 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt similarity index 87% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt index a002dc8ef..7764fda06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfoModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt @@ -18,14 +18,13 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.Nip11CachedRetriever -import com.vitorpamplona.ammolite.relays.RelayStats -import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter +import com.vitorpamplona.amethyst.service.replace import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -80,12 +79,8 @@ abstract class BasicRelaySetupInfoModel : ViewModel() { val relayList = getRelayList() ?: emptyList() relayList - .map { relayUrl -> - BasicRelaySetupInfo( - RelayUrlFormatter.normalize(relayUrl), - RelayStats.get(relayUrl), - ) - }.distinctBy { it.url } + .map { relaySetupInfoBuilder(it) } + .distinctBy { it.url } .sortedBy { it.relayStat.receivedBytes } .reversed() } @@ -112,6 +107,6 @@ abstract class BasicRelaySetupInfoModel : ViewModel() { relay: BasicRelaySetupInfo, paid: Boolean, ) { - _relays.update { it.updated(relay, relay.copy(paidRelay = paid)) } + _relays.update { it.replace(relay, relay.copy(paidRelay = paid)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayNameAndRemoveButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayNameAndRemoveButton.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt index eeba9d2d1..8b34ea3d5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayNameAndRemoveButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayStatusRow.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayStatusRow.kt index d5707a366..5170e7f28 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayStatusRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayStatusRow.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import android.widget.Toast import androidx.compose.foundation.gestures.detectTapGestures @@ -39,6 +39,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.service.countToHumanReadable +import com.vitorpamplona.amethyst.service.countToHumanReadableBytes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.allGoodColor diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt similarity index 72% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt index 3b33cba4c..4633e769a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt @@ -18,10 +18,12 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.MaterialTheme @@ -35,16 +37,27 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.ui.theme.placeholderText -import com.vitorpamplona.quartz.nip01Core.relay.RelayStat -import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter + +@Preview +@Composable +fun RelayUrlEditFieldPreview() { + ThemeComparisonColumn { + RelayUrlEditField {} + } +} @Composable -fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) { +fun RelayUrlEditField(onNewRelay: (String) -> Unit) { var url by remember { mutableStateOf("") } Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Size10dp)) { @@ -61,12 +74,28 @@ fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) { ) }, singleLine = true, + keyboardOptions = + KeyboardOptions.Default.copy( + autoCorrectEnabled = false, + imeAction = ImeAction.Go, + capitalization = KeyboardCapitalization.None, + keyboardType = KeyboardType.Text, + ), + keyboardActions = + KeyboardActions( + onGo = { + if (url.isNotBlank() && url != "/") { + onNewRelay(url) + url = "" + } + }, + ), ) Button( onClick = { if (url.isNotBlank() && url != "/") { - onNewRelay(BasicRelaySetupInfo(RelayUrlFormatter.normalize(url), RelayStat())) + onNewRelay(url) url = "" } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddDMRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddDMRelayListDialog.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt index e1f480662..5cf3c83af 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddDMRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -49,11 +49,11 @@ import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.imageModifier -import com.vitorpamplona.quartz.nip01Core.relay.RelayStat @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -153,7 +153,9 @@ fun ResetDMRelaysLonger(postViewModel: DMRelayListViewModel) { OutlinedButton( onClick = { postViewModel.deleteAll() - DefaultDMRelayList.forEach { postViewModel.addRelay(BasicRelaySetupInfo(it, RelayStat())) } + DefaultDMRelayList.forEach { + postViewModel.addRelay(relaySetupInfoBuilder(it)) + } postViewModel.loadRelayDocuments() }, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt index 4b79bf2ea..353ebc033 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -32,6 +32,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -71,6 +75,6 @@ fun LazyListScope.renderDMItems( item { Spacer(modifier = StdVertSpacer) - RelayUrlEditField { postViewModel.addRelay(it) } + RelayUrlEditField { postViewModel.addRelay(relaySetupInfoBuilder(it)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListViewModel.kt similarity index 89% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListViewModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListViewModel.kt index 5dff04edc..66114e113 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/DMRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListViewModel.kt @@ -18,7 +18,9 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm + +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel class DMRelayListViewModel : BasicRelaySetupInfoModel() { override fun getRelayList(): List? = account.getDMRelayList()?.relays() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3BasicRelaySetupInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3BasicRelaySetupInfo.kt new file mode 100644 index 000000000..ecaaf6896 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3BasicRelaySetupInfo.kt @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kind3 + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.ammolite.relays.FeedType +import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache +import com.vitorpamplona.quartz.nip01Core.relay.RelayStat + +@Immutable +data class Kind3BasicRelaySetupInfo( + val url: String, + val read: Boolean, + val write: Boolean, + val feedTypes: Set, + val relayStat: RelayStat, + val paidRelay: Boolean = false, +) { + val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3RelayListView.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3RelayListView.kt index 5a3a1d357..6e863b1a4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3RelayListView.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kind3 import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi @@ -70,11 +70,16 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever +import com.vitorpamplona.amethyst.service.countToHumanReadable +import com.vitorpamplona.amethyst.service.countToHumanReadableBytes import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.recommendations.Kind3RelayProposalSetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.recommendations.Kind3RelaySetupInfoProposalDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -90,10 +95,10 @@ import com.vitorpamplona.amethyst.ui.theme.allGoodColor import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.warningColor -import com.vitorpamplona.ammolite.relays.Constants import com.vitorpamplona.ammolite.relays.Constants.activeTypesGlobalChats import com.vitorpamplona.ammolite.relays.FeedType import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache +import com.vitorpamplona.ammolite.relays.RelayStats import com.vitorpamplona.quartz.nip01Core.relay.RelayStat import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter import kotlinx.coroutines.launch @@ -184,7 +189,7 @@ fun ServerConfigPreview() { sentBytes = 10000000, spamCounter = 10, ), - feedTypes = Constants.activeTypesGlobalChats, + feedTypes = activeTypesGlobalChats, paidRelay = true, ), onDelete = {}, @@ -790,13 +795,14 @@ fun Kind3RelayEditBox( Button( onClick = { if (url.isNotBlank() && url != "/") { + val normalized = RelayUrlFormatter.normalize(url) onNewRelay( Kind3BasicRelaySetupInfo( - url = RelayUrlFormatter.normalize(url), + url = normalized, read = read, write = write, feedTypes = activeTypesGlobalChats, - relayStat = RelayStat(), + relayStat = RelayStats.get(normalized), ), ) url = "" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3RelayListViewModel.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListViewModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3RelayListViewModel.kt index d5f6f80c5..348d5d87b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3RelayListViewModel.kt @@ -18,13 +18,16 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kind3 import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.Nip11CachedRetriever +import com.vitorpamplona.amethyst.service.replace +import com.vitorpamplona.amethyst.service.togglePresenceInSet +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.recommendations.Kind3RelayProposalSetupInfo import com.vitorpamplona.ammolite.relays.Constants import com.vitorpamplona.ammolite.relays.Constants.activeTypesGlobalChats import com.vitorpamplona.ammolite.relays.FeedType @@ -38,6 +41,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlin.collections.plus @Stable class Kind3RelayListViewModel : ViewModel() { @@ -206,7 +210,18 @@ class Kind3RelayListViewModel : ViewModel() { fun addRelay(relay: Kind3RelayProposalSetupInfo) { if (relays.value.any { it.url == relay.url }) return - _relays.update { it.plus(Kind3BasicRelaySetupInfo(relay.url, relay.read, relay.write, relay.feedTypes, relay.relayStat, relay.paidRelay)) } + _relays.update { + it.plus( + Kind3BasicRelaySetupInfo( + relay.url, + relay.read, + relay.write, + relay.feedTypes, + relay.relayStat, + relay.paidRelay, + ), + ) + } refreshProposals() @@ -230,42 +245,42 @@ class Kind3RelayListViewModel : ViewModel() { } fun toggleDownload(relay: Kind3BasicRelaySetupInfo) { - _relays.update { it.updated(relay, relay.copy(read = !relay.read)) } + _relays.update { it.replace(relay, relay.copy(read = !relay.read)) } hasModified = true } fun toggleUpload(relay: Kind3BasicRelaySetupInfo) { - _relays.update { it.updated(relay, relay.copy(write = !relay.write)) } + _relays.update { it.replace(relay, relay.copy(write = !relay.write)) } hasModified = true } fun toggleFollows(relay: Kind3BasicRelaySetupInfo) { - val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.FOLLOWS) - _relays.update { it.updated(relay, relay.copy(feedTypes = newTypes)) } + val newTypes = relay.feedTypes.togglePresenceInSet(FeedType.FOLLOWS) + _relays.update { it.replace(relay, relay.copy(feedTypes = newTypes)) } hasModified = true } fun toggleMessages(relay: Kind3BasicRelaySetupInfo) { - val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.PRIVATE_DMS) - _relays.update { it.updated(relay, relay.copy(feedTypes = newTypes)) } + val newTypes = relay.feedTypes.togglePresenceInSet(FeedType.PRIVATE_DMS) + _relays.update { it.replace(relay, relay.copy(feedTypes = newTypes)) } hasModified = true } fun togglePublicChats(relay: Kind3BasicRelaySetupInfo) { - val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.PUBLIC_CHATS) - _relays.update { it.updated(relay, relay.copy(feedTypes = newTypes)) } + val newTypes = relay.feedTypes.togglePresenceInSet(FeedType.PUBLIC_CHATS) + _relays.update { it.replace(relay, relay.copy(feedTypes = newTypes)) } hasModified = true } fun toggleGlobal(relay: Kind3BasicRelaySetupInfo) { - val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.GLOBAL) - _relays.update { it.updated(relay, relay.copy(feedTypes = newTypes)) } + val newTypes = relay.feedTypes.togglePresenceInSet(FeedType.GLOBAL) + _relays.update { it.replace(relay, relay.copy(feedTypes = newTypes)) } hasModified = true } fun toggleSearch(relay: Kind3BasicRelaySetupInfo) { - val newTypes = togglePresenceInSet(relay.feedTypes, FeedType.SEARCH) - _relays.update { it.updated(relay, relay.copy(feedTypes = newTypes)) } + val newTypes = relay.feedTypes.togglePresenceInSet(FeedType.SEARCH) + _relays.update { it.replace(relay, relay.copy(feedTypes = newTypes)) } hasModified = true } @@ -273,16 +288,6 @@ class Kind3RelayListViewModel : ViewModel() { relay: Kind3BasicRelaySetupInfo, paid: Boolean, ) { - _relays.update { it.updated(relay, relay.copy(paidRelay = paid)) } + _relays.update { it.replace(relay, relay.copy(paidRelay = paid)) } } } - -fun Iterable.updated( - old: T, - new: T, -): List = map { if (it == old) new else it } - -fun togglePresenceInSet( - set: Set, - item: T, -): Set = if (set.contains(item)) set.minus(item) else set.plus(item) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt index 47193e35e..362cd20b3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.local import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -32,6 +32,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -71,6 +75,6 @@ fun LazyListScope.renderLocalItems( item { Spacer(modifier = StdVertSpacer) - RelayUrlEditField { postViewModel.addRelay(it) } + RelayUrlEditField { postViewModel.addRelay(relaySetupInfoBuilder(it)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListViewModel.kt similarity index 89% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListViewModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListViewModel.kt index efde0c10d..e010dff3f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/LocalRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListViewModel.kt @@ -18,7 +18,9 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.local + +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel class LocalRelayListViewModel : BasicRelaySetupInfoModel() { override fun getRelayList(): List = account.settings.localRelayServers.toList() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt index fbe1c8b18..c69f3b514 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip37 import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -32,6 +32,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -71,6 +75,6 @@ fun LazyListScope.renderPrivateOutboxItems( item { Spacer(modifier = StdVertSpacer) - RelayUrlEditField { postViewModel.addRelay(it) } + RelayUrlEditField { postViewModel.addRelay(relaySetupInfoBuilder(it)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListViewModel.kt similarity index 89% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListViewModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListViewModel.kt index 92f2c9137..69dec48da 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/PrivateOutboxRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListViewModel.kt @@ -18,7 +18,9 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip37 + +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel class PrivateOutboxRelayListViewModel : BasicRelaySetupInfoModel() { override fun getRelayList(): List? = account.getPrivateOutboxRelayList()?.relays() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt similarity index 86% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt index d0c315fd2..283204936 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65 import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -32,6 +32,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -74,7 +78,7 @@ fun LazyListScope.renderNip65HomeItems( item { Spacer(modifier = StdVertSpacer) - RelayUrlEditField { postViewModel.addHomeRelay(it) } + RelayUrlEditField { postViewModel.addHomeRelay(relaySetupInfoBuilder(it)) } } } @@ -95,6 +99,6 @@ fun LazyListScope.renderNip65NotifItems( item { Spacer(modifier = StdVertSpacer) - RelayUrlEditField { postViewModel.addNotifRelay(it) } + RelayUrlEditField { postViewModel.addNotifRelay(relaySetupInfoBuilder(it)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt similarity index 88% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListViewModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt index 7306643e5..114da4bcd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Nip65RelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt @@ -18,16 +18,17 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65 import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.Nip11CachedRetriever -import com.vitorpamplona.ammolite.relays.RelayStats +import com.vitorpamplona.amethyst.service.replace +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent -import com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -110,12 +111,8 @@ class Nip65RelayListViewModel : ViewModel() { val relayList = account.getNIP65RelayList()?.writeRelays() ?: emptyList() relayList - .map { relayUrl -> - BasicRelaySetupInfo( - RelayUrlFormatter.normalize(relayUrl), - RelayStats.get(relayUrl), - ) - }.distinctBy { it.url } + .map { relaySetupInfoBuilder(it) } + .distinctBy { it.url } .sortedBy { it.relayStat.receivedBytes } .reversed() } @@ -124,12 +121,8 @@ class Nip65RelayListViewModel : ViewModel() { val relayList = account.getNIP65RelayList()?.readRelays() ?: emptyList() relayList - .map { relayUrl -> - BasicRelaySetupInfo( - RelayUrlFormatter.normalize(relayUrl), - RelayStats.get(relayUrl), - ) - }.distinctBy { it.url } + .map { relaySetupInfoBuilder(it) } + .distinctBy { it.url } .sortedBy { it.relayStat.receivedBytes } .reversed() } @@ -156,7 +149,7 @@ class Nip65RelayListViewModel : ViewModel() { relay: BasicRelaySetupInfo, paid: Boolean, ) { - _homeRelays.update { it.updated(relay, relay.copy(paidRelay = paid)) } + _homeRelays.update { it.replace(relay, relay.copy(paidRelay = paid)) } } fun addNotifRelay(relay: BasicRelaySetupInfo) { @@ -180,6 +173,6 @@ class Nip65RelayListViewModel : ViewModel() { relay: BasicRelaySetupInfo, paid: Boolean, ) { - _notificationRelays.update { it.updated(relay, relay.copy(paidRelay = paid)) } + _notificationRelays.update { it.replace(relay, relay.copy(paidRelay = paid)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelayProposalSetupInfo.kt similarity index 74% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfo.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelayProposalSetupInfo.kt index 0e82326b7..aa9472d0a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/BasicRelaySetupInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelayProposalSetupInfo.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.recommendations import androidx.compose.runtime.Immutable import com.vitorpamplona.ammolite.relays.FeedType @@ -26,27 +26,6 @@ import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.RelayStat -@Immutable -data class BasicRelaySetupInfo( - val url: String, - val relayStat: RelayStat, - val paidRelay: Boolean = false, -) { - val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url) -} - -@Immutable -data class Kind3BasicRelaySetupInfo( - val url: String, - val read: Boolean, - val write: Boolean, - val feedTypes: Set, - val relayStat: RelayStat, - val paidRelay: Boolean = false, -) { - val briefInfo: RelayBriefInfoCache.RelayBriefInfo = RelayBriefInfoCache.RelayBriefInfo(url) -} - @Immutable data class Kind3RelayProposalSetupInfo( val url: String, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelaySetupInfoProposalDialog.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalDialog.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelaySetupInfoProposalDialog.kt index b767d58aa..295aebfc4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelaySetupInfoProposalDialog.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.recommendations import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.RelayInfoDialog import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationDialog import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.ammolite.relays.RelayBriefInfoCache diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelaySetupInfoProposalRow.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalRow.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelaySetupInfoProposalRow.kt index c0e5233bb..3ded4f702 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/Kind3RelaySetupInfoProposalRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/recommendations/Kind3RelaySetupInfoProposalRow.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.recommendations import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddSearchRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/AddSearchRelayListDialog.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddSearchRelayListDialog.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/AddSearchRelayListDialog.kt index feff3bacc..daee2f64c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AddSearchRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/AddSearchRelayListDialog.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -49,11 +49,11 @@ import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.CloseButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.SaveButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.imageModifier -import com.vitorpamplona.quartz.nip01Core.relay.RelayStat @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -153,7 +153,9 @@ fun ResetSearchRelaysLonger(postViewModel: SearchRelayListViewModel) { OutlinedButton( onClick = { postViewModel.deleteAll() - DefaultSearchRelayList.forEach { postViewModel.addRelay(BasicRelaySetupInfo(it, RelayStat())) } + DefaultSearchRelayList.forEach { + postViewModel.addRelay(relaySetupInfoBuilder(it)) + } postViewModel.loadRelayDocuments() }, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt index 61484b404..d446b4520 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -32,6 +32,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.navigation.INav import com.vitorpamplona.amethyst.ui.navigation.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @@ -72,6 +76,6 @@ fun LazyListScope.renderSearchItems( item { Spacer(modifier = StdVertSpacer) - RelayUrlEditField { postViewModel.addRelay(it) } + RelayUrlEditField { postViewModel.addRelay(relaySetupInfoBuilder(it)) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListViewModel.kt similarity index 89% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListViewModel.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListViewModel.kt index cd1786216..7c469182d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/SearchRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListViewModel.kt @@ -18,7 +18,9 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.relays +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search + +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel class SearchRelayListViewModel : BasicRelaySetupInfoModel() { override fun getRelayList(): List? = account.getSearchRelayList()?.relays() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt index fc301a81d..2f1f73135 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Color.kt @@ -24,6 +24,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorMatrix +val Primary50 = Color(red = 127, green = 103, blue = 190) +val Primary60 = Color(red = 154, green = 130, blue = 219) +val Primary70 = Color(red = 182, green = 157, blue = 248) +val Primary80 = Color(red = 208, green = 188, blue = 255) + +val DEFAULT_PRIMARY = Color(red = 208, green = 188, blue = 255) +val LIGHT_PURPLE = Color(red = 187, green = 134, blue = 252) + val Purple200 = Color(0xFFBB86FC) val Purple500 = Color(0xFF6200EE) val Purple700 = Color(0xFF3700B3) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Preview.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Preview.kt new file mode 100644 index 000000000..c43681fb4 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Preview.kt @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2024 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.theme + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.model.ThemeType +import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel + +@Composable +fun ThemeComparisonColumn(toPreview: @Composable () -> Unit) { + Column { + Box { + val darkTheme: SharedPreferencesViewModel = viewModel() + darkTheme.updateTheme(ThemeType.DARK) + AmethystTheme(darkTheme) { + Surface(color = MaterialTheme.colorScheme.background) { toPreview() } + } + } + + Box { + val lightTheme: SharedPreferencesViewModel = viewModel() + lightTheme.updateTheme(ThemeType.LIGHT) + AmethystTheme(lightTheme) { + Surface(color = MaterialTheme.colorScheme.background) { toPreview() } + } + } + } +} + +@Composable +fun ThemeComparisonRow(toPreview: @Composable () -> Unit) { + Row { + Box(modifier = Modifier.weight(1f)) { + val darkTheme: SharedPreferencesViewModel = viewModel() + darkTheme.updateTheme(ThemeType.DARK) + AmethystTheme(darkTheme) { + Surface(color = MaterialTheme.colorScheme.background) { toPreview() } + } + } + + Box(modifier = Modifier.weight(1f)) { + val lightTheme: SharedPreferencesViewModel = viewModel() + lightTheme.updateTheme(ThemeType.LIGHT) + AmethystTheme(lightTheme) { + Surface(color = MaterialTheme.colorScheme.background) { toPreview() } + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 2e45260a1..cba3da2a1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -213,7 +213,6 @@ val CashuCardBorders = Modifier.fillMaxWidth().padding(10.dp).clip(shape = Quote val EditFieldModifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp, top = 5.dp).fillMaxWidth() val EditFieldTrailingIconModifier = Modifier.padding(start = 5.dp, end = 0.dp) -val EditFieldLeadingIconModifier = Modifier.height(32.dp).padding(start = 2.dp) val ZeroPadding = PaddingValues(0.dp) val FeedPadding = PaddingValues(top = 10.dp, bottom = 10.dp) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 19e0126ae..a4f60bb7b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -23,19 +23,18 @@ package com.vitorpamplona.amethyst.ui.theme import android.app.Activity import android.app.UiModeManager import android.content.Context +import android.graphics.Color.red import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.ColorScheme import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface import androidx.compose.material3.darkColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable @@ -55,7 +54,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.view.WindowCompat -import androidx.lifecycle.viewmodel.compose.viewModel import com.halilibo.richtext.ui.RichTextStyle import com.halilibo.richtext.ui.resolveDefaults import com.patrykandpatrick.vico.compose.common.VicoTheme @@ -68,8 +66,9 @@ private val DarkColorPalette = primary = Purple200, secondary = Teal200, tertiary = Teal200, - background = Color(red = 0, green = 0, blue = 0), - surface = Color(red = 0, green = 0, blue = 0), + background = Color.Black, // full black theme + surface = Color.Black, // full black theme + surfaceDim = Color.Black, // full black theme surfaceVariant = Color(red = 29, green = 26, blue = 34), ) @@ -78,6 +77,7 @@ private val LightColorPalette = primary = Purple500, secondary = Teal200, tertiary = Teal200, + surfaceContainerHighest = Color(red = 236, green = 230, blue = 240), surfaceVariant = Color(red = 250, green = 245, blue = 252), ) @@ -273,6 +273,20 @@ val DarkLargeRelayIconModifier = .size(Size55dp) .clip(shape = CircleShape) +val darkLargeProfilePictureModifier = + Modifier + .width(120.dp) + .height(120.dp) + .clip(shape = CircleShape) + .border(3.dp, DarkColorPalette.background, CircleShape) + +val lightLargeProfilePictureModifier = + Modifier + .width(120.dp) + .height(120.dp) + .clip(shape = CircleShape) + .border(3.dp, LightColorPalette.background, CircleShape) + val RichTextDefaults = RichTextStyle().resolveDefaults() val MarkDownStyleOnDark = @@ -447,6 +461,9 @@ val ColorScheme.largeRelayIconModifier: Modifier val ColorScheme.selectedReactionBoxModifier: Modifier get() = if (isLight) LightSelectedReactionBoxModifier else DarkSelectedReactionBoxModifier +val ColorScheme.largeProfilePictureModifier: Modifier + get() = if (isLight) lightLargeProfilePictureModifier else darkLargeProfilePictureModifier + val chartLightColors = VicoTheme( candlestickCartesianLayerColors = @@ -521,45 +538,3 @@ fun AmethystTheme( } } } - -@Composable -fun ThemeComparisonColumn(toPreview: @Composable () -> Unit) { - Column { - Box { - val darkTheme: SharedPreferencesViewModel = viewModel() - darkTheme.updateTheme(ThemeType.DARK) - AmethystTheme(darkTheme) { - Surface(color = MaterialTheme.colorScheme.background) { toPreview() } - } - } - - Box { - val lightTheme: SharedPreferencesViewModel = viewModel() - lightTheme.updateTheme(ThemeType.LIGHT) - AmethystTheme(lightTheme) { - Surface(color = MaterialTheme.colorScheme.background) { toPreview() } - } - } - } -} - -@Composable -fun ThemeComparisonRow(toPreview: @Composable () -> Unit) { - Row { - Box(modifier = Modifier.weight(1f)) { - val darkTheme: SharedPreferencesViewModel = viewModel() - darkTheme.updateTheme(ThemeType.DARK) - AmethystTheme(darkTheme) { - Surface(color = MaterialTheme.colorScheme.background) { toPreview() } - } - } - - Box(modifier = Modifier.weight(1f)) { - val lightTheme: SharedPreferencesViewModel = viewModel() - lightTheme.updateTheme(ThemeType.LIGHT) - AmethystTheme(lightTheme) { - Surface(color = MaterialTheme.colorScheme.background) { toPreview() } - } - } - } -} diff --git a/amethyst/src/main/res/values-cs/strings.xml b/amethyst/src/main/res/values-cs/strings.xml index 336a81b44..1da6feca0 100644 --- a/amethyst/src/main/res/values-cs/strings.xml +++ b/amethyst/src/main/res/values-cs/strings.xml @@ -212,6 +212,13 @@ Kanál vytvořen "Informace kanálu změněna na" Veřejný chat + Metadata veřejného chatu + Veřejné konverzace jsou viditelné pro všechny na Nostru a kdokoli + se na nich může podílet. Jsou skvělé pro otevřené komunity kolem konkrétních témat. + Moderaci lze ovládat smazáním příspěvků na relé + Rele + Vložte mezi 1-3 relé, které hostí tuto skupinu. + Klienti Nostr používají toto nastavení, aby věděli, kam stahovat zprávy a odesílat zprávy. příspěvků přijato Odebrat Automaticky diff --git a/amethyst/src/main/res/values-de/strings.xml b/amethyst/src/main/res/values-de/strings.xml index 7914be591..5e2dee8be 100644 --- a/amethyst/src/main/res/values-de/strings.xml +++ b/amethyst/src/main/res/values-de/strings.xml @@ -216,6 +216,13 @@ anz der Bedingungen ist erforderlich Kanal erstellt "Kanalinformationen geändert in" Öffentlicher Chat + Öffentliche Chat Metadaten + Öffentliche Chats sind für jeden auf Nostr sichtbar und jeder + kann daran teilnehmen. Sie eignen sich hervorragend für offene Gemeinschaften rund um bestimmte Themen. + Moderation kann durch Löschen von Beiträgen auf den Relais gesteuert werden + Relais + Fügen Sie zwischen 1-3 Relais ein, die diese Gruppe beherbergen. + Nostr Clients verwenden diese Einstellung, um zu wissen, woher sie Nachrichten herunterladen und an welche gesendet werden sollen. empfangene Beiträge Entfernen Automatisch diff --git a/amethyst/src/main/res/values-hu/strings.xml b/amethyst/src/main/res/values-hu/strings.xml index afec08f2f..35fe3141a 100644 --- a/amethyst/src/main/res/values-hu/strings.xml +++ b/amethyst/src/main/res/values-hu/strings.xml @@ -212,6 +212,13 @@ Csatorna létrehozva "A csatornainformáció a következőre módosult:" Nyilvános csevegés + Nyilvános csevegés metaadatai + A nyilvános csevegések mindenki számára láthatóak a Nostr-on, és bárki + részt vehet bennük. Ezek kiválóan alkalmasak bizonyos témák köré szerveződő nyílt közösségek számára. + A moderálás az átjátszókon lévő hozzászólások törlésével szabályozható + Átjátszók + Adjon hozzá 1-3 olyan átjátszót, amelyek kiszolgálják ezt a csoportot. + A Nostr-kliensek ezt a beállítást használják arra, hogy tudják, honnan töltsék le az üzeneteket, és hová küldjék az üzeneteket. fogadott bejegyzések Eltávolítás Automatikus @@ -865,6 +872,7 @@ Változások összefoglalása Gyors javítások… Javaslat elfogadása + Videó lejátszása felugró ablakban Letöltés Feliratozás bekapcsolva Feliratozás kikapcsolva diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml index 265f04fef..9f7fbaf0f 100644 --- a/amethyst/src/main/res/values-pl-rPL/strings.xml +++ b/amethyst/src/main/res/values-pl-rPL/strings.xml @@ -205,6 +205,7 @@ z opisem i zdjęcie zmieniono nazwę czatu na + Nowy profil czatu: opis dla i zdjęcie do Wyjdź @@ -212,6 +213,10 @@ Kanał utworzony "Informacje o kanale zmienione na" Czat Publiczny + Metadane Czatu Publicznego + Czaty publiczne są widoczne dla wszystkich użytkowników Nostr i każdy może w nich uczestniczyć. Są idealne dla otwartych społeczności skupionych wokół konkretnych tematów. Moderację można kontrolować, usuwając posty z transmiterów + Transmitery + Wstaw od 1 do 3 transmiterów, które obsługują tę grupę. Klienci Nostr używają tego ustawienia, aby wiedzieć, skąd pobierać wiadomości i do kogo je wysyłać. odebranych wiadomości Usuń Automatycznie @@ -418,7 +423,7 @@ Port Socks Orbota Aktywny silnik Tor Użyj wersji wewnętrznej lub Orbota - TOR wstępne ustawienia prywatności + Tor wstępne ustawienia prywatności Szybka modyfikacja wszystkich ustawień poniżej Adres Onion /Transmitery Użyj Tor dla dowolnego adresu .onion @@ -519,6 +524,7 @@ Wyślij do Zap Wallet Otwórz w Portfelu Cashu Skopiuj token + Otwórz w innej aplikacji Nie ustawiono adresu Lightning Skopiowano token do schowka NA ŻYWO @@ -865,6 +871,7 @@ Podsumowanie zmian Szybkie poprawki… Zaakceptuj sugestię + Uruchom wideo w pop-upie Pobierz Tekst włączony Tekst wyłączony diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml index b8d043a8d..3c46fdfa8 100644 --- a/amethyst/src/main/res/values-pt-rBR/strings.xml +++ b/amethyst/src/main/res/values-pt-rBR/strings.xml @@ -212,6 +212,13 @@ Canal criado "Informação do canal mudou para" Chat público + Metadados do Chat Público + Os chats públicos são visíveis para todos no Nostr, e qualquer pessoa + pode participar deles. Eles são ótimos para comunidades abertas em torno de tópicos específicos. + A moderação pode ser controlada excluindo postagens em seus relays + Relés + Inserir entre 1-3 relés que hospedam esse grupo. + os clientes Nostr usam essa configuração para saber de onde fazer o download de mensagens e enviar suas mensagens. postagens recebidas Remover Automaticamente diff --git a/amethyst/src/main/res/values-sv-rSE/strings.xml b/amethyst/src/main/res/values-sv-rSE/strings.xml index 40eef6e3c..cf72cefd6 100644 --- a/amethyst/src/main/res/values-sv-rSE/strings.xml +++ b/amethyst/src/main/res/values-sv-rSE/strings.xml @@ -212,6 +212,13 @@ Kanal skapad "Kanalinformation ändrad till" Publik Chat + Metadata för offentlig chatt + Offentliga chattar är synliga för alla på Nostr och alla + kan delta på dem. De är bra för öppna samhällen kring specifika ämnen. + Moderation kan kontrolleras genom att ta bort inlägg på reläerna + Reläer + Infoga mellan 1-3 reläer som är värd för denna grupp. + Nostr klienter använder denna inställning för att veta var du kan ladda ner meddelanden från och skicka dina meddelanden till. mottagna inlägg Ta bort Automatiskt diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 5f73c53be..187985cdf 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -213,6 +213,7 @@ with description of and picture changed chat name to + New chat profile: description to and picture to Leave @@ -220,6 +221,15 @@ Channel created "Channel Information changed to" Public Chat + Public Chat Metadata + Public chats are visible to everyone on Nostr and anyone + can participate on them. They are great for open communities around specific topics. + Moderation can be controlled by deleting posts on the its relays + + Relays + Insert between 1-3 relays that host this group. + Nostr clients use this setting to know where to download messages from and send your messages to. + posts received Remove sats @@ -607,6 +617,8 @@ Open in Cashu Wallet Copy Token + Open in Another App + No Lightning Address set Copied token to clipboard diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 11b4ace86..23ca61aba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,11 +16,11 @@ coil = "3.1.0" composeBom = "2025.03.00" coreKtx = "1.15.0" espressoCore = "3.6.1" -firebaseBom = "33.10.0" +firebaseBom = "33.11.0" fragmentKtx = "1.8.6" gms = "4.4.2" -jacksonModuleKotlin = "2.18.2" -jna = "5.16.0" +jacksonModuleKotlin = "2.18.3" +jna = "5.17.0" jtorctl = "0.4.5.7" junit = "4.13.2" kotlin = "2.1.0" @@ -46,9 +46,9 @@ torAndroid = "0.4.8.12" translate = "17.0.3" unifiedpush = "2.3.1" urlDetector = "0.1.23" -vico-charts = "2.0.3" +vico-charts = "2.1.1" zelory = "3.0.1" -zoomable = "2.3.0" +zoomable = "2.4.0" zxing = "3.5.3" zxingAndroidEmbedded = "4.3.0" windowCoreAndroid = "1.3.0" diff --git a/quartz/build.gradle b/quartz/build.gradle index ef440c1a9..12e9db7ed 100644 --- a/quartz/build.gradle +++ b/quartz/build.gradle @@ -59,7 +59,7 @@ dependencies { // LibSodium for ChaCha encryption (NIP-44) // Wait for @aar support in version catalogs implementation "com.goterl:lazysodium-android:5.1.0@aar" - implementation 'net.java.dev.jna:jna:5.16.0@aar' + implementation 'net.java.dev.jna:jna:5.17.0@aar' //implementation (libs.lazysodium.android) { artifact { type = "aar" } } //implementation (libs.jna) { artifact { type = "aar" } }