From b74bf44141ad17f6dff3073d780c671e43105104 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 25 Mar 2025 14:36:12 -0400 Subject: [PATCH 01/12] - Migrates NIP-28 creation to its own route. - Adds picture upload for NIP-28 metadata - Adds support for relay hints on NIP-28 - Improves edit field for chats - Improves keyboard support for relay lists edit fields --- .../vitorpamplona/amethyst/model/Account.kt | 43 ++- .../ui/actions/NewUserMetadataScreen.kt | 2 +- ...RelayListView.kt => AllRelayListScreen.kt} | 2 +- .../ui/actions/relays/RelayUrlEditField.kt | 31 ++ .../ui/actions/uploads/SelectFromGallery.kt | 32 +- .../amethyst/ui/navigation/AppNavigation.kt | 21 +- .../amethyst/ui/navigation/Routes.kt | 14 + .../ui/screen/loggedIn/NewPostScreen.kt | 14 +- .../send/PrivateMessageEditFieldRow.kt | 19 +- .../header/LongChannelActionOptions.kt | 2 +- .../header/actions/EditChatButton.kt | 14 +- .../metadata/ChannelMetadataDialog.kt | 169 ---------- .../metadata/ChannelMetadataScreen.kt | 317 ++++++++++++++++++ .../metadata/ChannelMetadataViewModel.kt | 157 ++++++++- .../chats/publicChannels/send/EditFieldRow.kt | 6 +- .../loggedIn/chats/rooms/ChannelFabColumn.kt | 16 +- .../rooms/singlepane/MessagesSinglePane.kt | 2 +- .../chats/rooms/twopane/MessagesTwoPane.kt | 2 +- .../vitorpamplona/amethyst/ui/theme/Shape.kt | 1 - amethyst/src/main/res/values/strings.xml | 9 + 20 files changed, 602 insertions(+), 271 deletions(-) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/{AllRelayListView.kt => AllRelayListScreen.kt} (99%) delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataDialog.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt 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 f6cfb9f61..fe27f7b40 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -126,8 +126,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 @@ -2311,10 +2309,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) } } @@ -2804,28 +2821,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/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/relays/AllRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListScreen.kt index 24549b416..08a0785be 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListScreen.kt @@ -64,7 +64,7 @@ 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, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt index 3b33cba4c..17c4d75b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayUrlEditField.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.actions.relays 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,14 +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) { var url by remember { mutableStateOf("") } @@ -61,6 +76,22 @@ 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(BasicRelaySetupInfo(RelayUrlFormatter.normalize(url), RelayStat())) + url = "" + } + }, + ), ) Button( 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/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index b07bbaba7..c761a6701 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,7 @@ 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.actions.relays.AllRelayListScreen import com.vitorpamplona.amethyst.ui.components.DisplayNotifyMessages import com.vitorpamplona.amethyst.ui.components.getActivity import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages @@ -61,6 +61,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 @@ -260,6 +261,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, @@ -304,7 +321,7 @@ fun AppNavigation( content = { val relayToAdd = it.arguments?.getString("toAdd") - AllRelayListView( + AllRelayListScreen( relayToAdd = relayToAdd, accountViewModel = accountViewModel, nav = nav, 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 f3900a5e2..8927aefb5 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 @@ -206,6 +206,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/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/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 index 84a146658..2bc588cc0 100644 --- 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 @@ -89,7 +89,7 @@ fun LongChannelActionOptions( } if (isMe) { - EditButton(channel, accountViewModel) + EditButton(channel, accountViewModel, nav) } WatchChannelFollows(channel, accountViewModel) { isFollowing -> 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..a187629aa 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 @@ -28,16 +28,15 @@ import androidx.compose.material3.Button 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,19 +44,14 @@ 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( modifier = Modifier .padding(horizontal = 3.dp) .width(50.dp), - onClick = { wantsToPost = true }, + onClick = { nav.nav("ChannelMetadataEdit?id=${channel.idHex}") }, contentPadding = ZeroPadding, ) { Icon( 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..1cfadca41 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt @@ -0,0 +1,317 @@ +/** + * 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.relays.BasicRelaySetupInfoDialog +import com.vitorpamplona.amethyst.ui.actions.relays.RelayUrlEditField +import com.vitorpamplona.amethyst.ui.actions.relays.SettingsCategory +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.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(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..6bfd63b15 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,39 @@ */ 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.relays.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.ammolite.relays.RelayStats 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 com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter 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.plus +import kotlin.coroutines.cancellation.CancellationException class ChannelMetadataViewModel : ViewModel() { private var account: Account? = null @@ -41,6 +62,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 +81,23 @@ 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 { + BasicRelaySetupInfo( + RelayUrlFormatter.normalize(it), + RelayStats.get(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 +107,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 +133,7 @@ class ChannelMetadataViewModel : ViewModel() { channelName.value.text, channelDescription.value.text, channelPicture.value.text, - null, + channelRelays.value.map { it.url }, hint, ) } else { @@ -89,12 +143,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 +165,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/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index f1d701362..53bdab103 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 @@ -211,7 +211,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/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 7a0ebe232..fb58c530a 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -220,6 +220,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 From a23ad2dde74129db2fb4201a91dcc98b9bafef57 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 25 Mar 2025 15:17:59 -0400 Subject: [PATCH 02/12] Moves relay screens to it's own master package on the UI and reorganizes subpackages --- .../com/vitorpamplona/amethyst/model/Note.kt | 14 ++--- .../amethyst/service/ByteFormatter.kt | 39 +++++++++++++ .../CountFormatter.kt} | 30 +++------- .../amethyst/service/IterableExt.kt | 26 +++++++++ .../vitorpamplona/amethyst/service/SetExt.kt | 23 ++++++++ .../ui/actions/RelaySelectionDialog.kt | 2 +- .../mediaServers/AllMediaServersLIstView.kt | 4 +- .../ui/dal/ChatroomListKnownFeedFilter.kt | 6 +- .../ui/dal/ChatroomListNewFeedFilter.kt | 4 +- .../amethyst/ui/navigation/AppNavigation.kt | 2 +- .../amethyst/ui/note/RelayListRow.kt | 2 +- .../note/elements/AddInboxRelayForDMCard.kt | 2 +- .../elements/AddInboxRelayForSearchCard.kt | 2 +- .../amethyst/ui/note/types/Torrent.kt | 2 +- .../amethyst/ui/note/types/TorrentComment.kt | 2 +- .../metadata/ChannelMetadataScreen.kt | 9 +-- .../metadata/ChannelMetadataViewModel.kt | 15 ++--- .../loggedIn}/relays/AllRelayListScreen.kt | 28 ++++++++-- .../relays/RelayInformationDialog.kt | 2 +- .../relays/common/BasicRelaySetupInfo.kt | 44 +++++++++++++++ .../BasicRelaySetupInfoClickableRow.kt | 2 +- .../common}/BasicRelaySetupInfoDialog.kt | 3 +- .../common}/BasicRelaySetupInfoModel.kt | 15 ++--- .../common}/RelayNameAndRemoveButton.kt | 2 +- .../loggedIn/relays/common}/RelayStatusRow.kt | 4 +- .../relays/common}/RelayUrlEditField.kt | 10 ++-- .../relays/dm}/AddDMRelayListDialog.kt | 8 ++- .../loggedIn/relays/dm}/DMRelayListView.kt | 8 ++- .../relays/dm}/DMRelayListViewModel.kt | 4 +- .../relays/kind3/Kind3BasicRelaySetupInfo.kt | 38 +++++++++++++ .../relays/kind3}/Kind3RelayListView.kt | 16 ++++-- .../relays/kind3}/Kind3RelayListViewModel.kt | 55 ++++++++++--------- .../relays/local}/LocalRelayListView.kt | 8 ++- .../relays/local}/LocalRelayListViewModel.kt | 4 +- .../nip37}/PrivateOutboxRelayListView.kt | 8 ++- .../nip37}/PrivateOutboxRelayListViewModel.kt | 4 +- .../relays/nip65}/Nip65RelayListView.kt | 10 +++- .../relays/nip65}/Nip65RelayListViewModel.kt | 27 ++++----- .../Kind3RelayProposalSetupInfo.kt} | 23 +------- .../Kind3RelaySetupInfoProposalDialog.kt | 3 +- .../Kind3RelaySetupInfoProposalRow.kt | 2 +- .../search}/AddSearchRelayListDialog.kt | 8 ++- .../relays/search}/SearchRelayListView.kt | 8 ++- .../search}/SearchRelayListViewModel.kt | 4 +- 44 files changed, 359 insertions(+), 173 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/ByteFormatter.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/{ui/actions/relays/ByteFormatter.kt => service/CountFormatter.kt} (55%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/IterableExt.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/SetExt.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions => screen/loggedIn}/relays/AllRelayListScreen.kt (88%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions => screen/loggedIn}/relays/RelayInformationDialog.kt (99%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/common}/BasicRelaySetupInfoClickableRow.kt (98%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/common}/BasicRelaySetupInfoDialog.kt (97%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/common}/BasicRelaySetupInfoModel.kt (87%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/common}/RelayNameAndRemoveButton.kt (98%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/common}/RelayStatusRow.kt (97%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/common}/RelayUrlEditField.kt (90%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/dm}/AddDMRelayListDialog.kt (95%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/dm}/DMRelayListView.kt (85%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/dm}/DMRelayListViewModel.kt (89%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/kind3/Kind3BasicRelaySetupInfo.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/kind3}/Kind3RelayListView.kt (97%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/kind3}/Kind3RelayListViewModel.kt (85%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/local}/LocalRelayListView.kt (85%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/local}/LocalRelayListViewModel.kt (89%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/nip37}/PrivateOutboxRelayListView.kt (85%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/nip37}/PrivateOutboxRelayListViewModel.kt (89%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/nip65}/Nip65RelayListView.kt (86%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/nip65}/Nip65RelayListViewModel.kt (88%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays/BasicRelaySetupInfo.kt => screen/loggedIn/relays/recommendations/Kind3RelayProposalSetupInfo.kt} (74%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/recommendations}/Kind3RelaySetupInfoProposalDialog.kt (96%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/recommendations}/Kind3RelaySetupInfoProposalRow.kt (98%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/search}/AddSearchRelayListDialog.kt (95%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/search}/SearchRelayListView.kt (85%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{actions/relays => screen/loggedIn/relays/search}/SearchRelayListViewModel.kt (89%) 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/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/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 c761a6701..47b4e7b47 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.AllRelayListScreen import com.vitorpamplona.amethyst.ui.components.DisplayNotifyMessages import com.vitorpamplona.amethyst.ui.components.getActivity import com.vitorpamplona.amethyst.ui.components.toasts.DisplayErrorMessages @@ -72,6 +71,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.HomeScreen 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 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/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/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/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt index 1cfadca41..715f8b1af 100644 --- 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 @@ -54,9 +54,6 @@ 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.relays.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.actions.relays.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.actions.relays.SettingsCategory import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.ui.navigation.EmptyNav import com.vitorpamplona.amethyst.ui.navigation.INav @@ -67,6 +64,10 @@ 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 @@ -236,7 +237,7 @@ private fun ChannelMetadataScaffold( } item { - RelayUrlEditField { postViewModel.addHomeRelay(it) } + RelayUrlEditField { postViewModel.addHomeRelay(relaySetupInfoBuilder(it)) } Spacer(modifier = DoubleVertSpacer) } 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 6bfd63b15..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 @@ -37,20 +37,21 @@ 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.relays.BasicRelaySetupInfo 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.ammolite.relays.RelayStats 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 com.vitorpamplona.quartz.nip65RelayList.RelayUrlFormatter 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 @@ -84,12 +85,8 @@ class ChannelMetadataViewModel : ViewModel() { val relays = channel.info.relays - ?.map { - BasicRelaySetupInfo( - RelayUrlFormatter.normalize(it), - RelayStats.get(it), - ) - }?.distinctBy { it.url } + ?.map { relaySetupInfoBuilder(it) } + ?.distinctBy { it.url } _channelRelays.update { relays ?: emptyList() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListScreen.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/AllRelayListScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt index 08a0785be..e52a465db 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/AllRelayListScreen.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,7 +76,6 @@ 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 AllRelayListScreen( @@ -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 90% 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 17c4d75b9..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,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.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -47,8 +47,6 @@ 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 @@ -59,7 +57,7 @@ fun RelayUrlEditFieldPreview() { } @Composable -fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) { +fun RelayUrlEditField(onNewRelay: (String) -> Unit) { var url by remember { mutableStateOf("") } Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(Size10dp)) { @@ -87,7 +85,7 @@ fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) { KeyboardActions( onGo = { if (url.isNotBlank() && url != "/") { - onNewRelay(BasicRelaySetupInfo(RelayUrlFormatter.normalize(url), RelayStat())) + onNewRelay(url) url = "" } }, @@ -97,7 +95,7 @@ fun RelayUrlEditField(onNewRelay: (BasicRelaySetupInfo) -> Unit) { 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() From 55182938136b0dfa376b1e4a2a9363a71232b599 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Tue, 25 Mar 2025 19:54:15 +0000 Subject: [PATCH 03/12] use existing helper function that URL decodes message text --- .../vitorpamplona/amethyst/ui/navigation/AppNavigation.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 47b4e7b47..589a7c4f5 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 @@ -337,7 +337,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) @@ -347,8 +347,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, From 7ad47bb137f8f1d8e6549af8ca04f5e500363858 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Tue, 25 Mar 2025 21:55:12 +0000 Subject: [PATCH 04/12] Update README.md remove fdroid from readme --- README.md | 3 --- 1 file changed, 3 deletions(-) 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) From 8f77ecd891883ae99f99f57329e0a81ab6e0a5c9 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 25 Mar 2025 19:36:39 -0400 Subject: [PATCH 05/12] Improves the rendering of the channel metadata changes Redesigns the expandable top bar for NIP-28 chats --- .../vitorpamplona/amethyst/model/Channel.kt | 29 +- .../amethyst/model/LocalCache.kt | 4 +- .../TopBarExtensibleWithBackButton.kt | 8 +- .../types/RenderChangeChannelMetadataNote.kt | 41 +-- .../feed/types/RenderCreateChannelNote.kt | 331 ++++++++++++++++-- .../header/LongChannelActionOptions.kt | 120 ------- .../header/LongPublicChatChannelHeader.kt | 112 +++++- .../header/PublicChatTopBar.kt | 1 + .../header/ShortPublicChatChannelHeader.kt | 41 +++ .../header/actions/EditChatButton.kt | 4 +- .../header/actions/JoinChatButton.kt | 4 +- .../header/actions/LeaveChatButton.kt | 4 +- .../header/actions/LinkChatButton.kt | 89 +++++ .../header/actions/OpenChatButton.kt | 74 ++++ .../header/actions/ShareChatButton.kt | 86 +++++ .../ui/screen/loggedIn/qrcode/ShowQRDialog.kt | 12 +- .../vitorpamplona/amethyst/ui/theme/Theme.kt | 19 + amethyst/src/main/res/values/strings.xml | 3 + 18 files changed, 780 insertions(+), 202 deletions(-) delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/LongChannelActionOptions.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LinkChatButton.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/OpenChatButton.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/ShareChatButton.kt 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 be5342272..541159080 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/ui/navigation/TopBarExtensibleWithBackButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt index 29d165f84..21d59394c 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,7 @@ 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.Surface import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf @@ -90,11 +90,7 @@ fun MyExtensibleTopAppBar( ) if (expanded.value && extendableRow != null) { - Row( - Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - ) { + Surface(modifier = Modifier.fillMaxWidth(), tonalElevation = 10.dp) { Column { extendableRow() } } } 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/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 2bc588cc0..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, nav) - } - - 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 a187629aa..9057a8575 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,7 +24,7 @@ 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 @@ -46,7 +46,7 @@ fun EditButton( accountViewModel: AccountViewModel, nav: INav, ) { - Button( + FilledTonalButton( modifier = Modifier .padding(horizontal = 3.dp) 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..ef226dc94 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,7 +20,7 @@ */ 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 @@ -38,7 +38,7 @@ fun JoinChatButton( accountViewModel: AccountViewModel, nav: INav, ) { - Button( + FilledTonalButton( modifier = HalfHalfHorzModifier, onClick = { accountViewModel.follow(channel) }, contentPadding = ButtonPadding, 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..ae3d9ee54 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,7 +20,7 @@ */ 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 @@ -38,7 +38,7 @@ fun LeaveChatButton( accountViewModel: AccountViewModel, nav: INav, ) { - Button( + FilledTonalButton( modifier = HalfHalfHorzModifier, onClick = { accountViewModel.unfollow(channel) }, contentPadding = ButtonPadding, 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..49ab4ce37 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/LinkChatButton.kt @@ -0,0 +1,89 @@ +/** + * 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.graphics.Color +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( + tint = Color.White, + 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..1b493f3a6 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/OpenChatButton.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.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.graphics.Color +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( + tint = Color.White, + 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..a3cc291e4 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/header/actions/ShareChatButton.kt @@ -0,0 +1,86 @@ +/** + * 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.graphics.Color +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( + tint = Color.White, + 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/qrcode/ShowQRDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRDialog.kt index 7d360ad6f..074281646 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,7 +20,6 @@ */ 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 @@ -28,8 +27,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height 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.FilledTonalButton import androidx.compose.material3.IconButton @@ -43,7 +40,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 @@ -65,6 +61,7 @@ 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.largeProfilePictureModifier import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata @Preview @@ -135,12 +132,7 @@ fun ShowQRDialog( 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), + modifier = MaterialTheme.colorScheme.largeProfilePictureModifier, loadProfilePicture = accountViewModel.settings.showProfilePictures.value, loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, ) 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..00594bab8 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 @@ -30,8 +30,10 @@ 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 @@ -273,6 +275,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 +463,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 = diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index fb58c530a..463a5b749 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 @@ -616,6 +617,8 @@ Open in Cashu Wallet Copy Token + Open in Another App + No Lightning Address set Copied token to clipboard From 0d623b2eccfc6972be133a7a4e6d8b205ec7e432 Mon Sep 17 00:00:00 2001 From: David Kaspar Date: Sat, 22 Mar 2025 14:57:27 +0000 Subject: [PATCH 06/12] Use CheckNewAndRenderNote method for FileHeaderEvent, FileStorageHeaderEvent and VideoEvent(s) --- .../java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt | 5 ----- 1 file changed, 5 deletions(-) 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( From 04a161dc470ed72a0851e239236bb772c26395eb Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Wed, 26 Mar 2025 15:20:27 +0000 Subject: [PATCH 07/12] New Crowdin translations by GitHub Action --- amethyst/src/main/res/values-cs/strings.xml | 7 +++++++ amethyst/src/main/res/values-de/strings.xml | 7 +++++++ amethyst/src/main/res/values-hu/strings.xml | 8 ++++++++ amethyst/src/main/res/values-pt-rBR/strings.xml | 7 +++++++ amethyst/src/main/res/values-sv-rSE/strings.xml | 7 +++++++ 5 files changed, 36 insertions(+) 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-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 From aac366c690cc96c00e038ae6ee97a50a3b5e6d17 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 26 Mar 2025 11:22:27 -0400 Subject: [PATCH 08/12] - Migrates "Show Anyway" and "Show More" buttons to lighter version (Tonal) - Improves tonal rendering on Light theme - Lightens card surfaces - Adjusts surfaceTint on Dark theme to match the elevation differences on the Light theme --- .../amethyst/ui/components/CashuRedeem.kt | 21 +++--- .../ui/components/ExpandableRichTextViewer.kt | 13 +--- .../ui/components/SensitivityWarning.kt | 10 +-- .../TopBarExtensibleWithBackButton.kt | 5 +- .../amethyst/ui/note/BlankNote.kt | 20 ++--- .../vitorpamplona/amethyst/ui/note/Icons.kt | 12 +-- .../amethyst/ui/note/RelayCompose.kt | 20 ++--- .../amethyst/ui/note/UserCompose.kt | 4 +- .../amethyst/ui/note/ZapNoteCompose.kt | 3 - .../ui/note/elements/AddRemoveButtons.kt | 11 ++- .../privateDM/header/RenderRoomTopBar.kt | 8 +- .../header/actions/EditChatButton.kt | 2 - .../header/actions/JoinChatButton.kt | 3 +- .../header/actions/LeaveChatButton.kt | 3 +- .../header/actions/LinkChatButton.kt | 2 - .../header/actions/OpenChatButton.kt | 2 - .../header/actions/ShareChatButton.kt | 2 - .../screen/loggedIn/profile/FollowButtons.kt | 21 ++---- .../loggedIn/profile/header/EditButton.kt | 6 +- .../loggedIn/profile/header/MessageButton.kt | 6 +- .../loggedIn/profile/zaps/ShowUserButton.kt | 7 +- .../vitorpamplona/amethyst/ui/theme/Color.kt | 8 ++ .../amethyst/ui/theme/Preview.kt | 74 +++++++++++++++++++ .../vitorpamplona/amethyst/ui/theme/Theme.kt | 54 ++------------ 24 files changed, 142 insertions(+), 175 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Preview.kt 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/navigation/TopBarExtensibleWithBackButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/TopBarExtensibleWithBackButton.kt index 21d59394c..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 @@ -29,6 +29,7 @@ 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 @@ -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,7 +92,8 @@ fun MyExtensibleTopAppBar( ) if (expanded.value && extendableRow != null) { - Surface(modifier = Modifier.fillMaxWidth(), tonalElevation = 10.dp) { + 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/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/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/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/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/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 9057a8575..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 @@ -30,7 +30,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue 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 @@ -55,7 +54,6 @@ fun EditButton( 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 ef226dc94..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 @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28 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 @@ -43,6 +42,6 @@ fun JoinChatButton( 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 ae3d9ee54..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 @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28 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 @@ -43,6 +42,6 @@ fun LeaveChatButton( 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 index 49ab4ce37..7d9949609 100644 --- 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 @@ -29,7 +29,6 @@ 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.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat @@ -80,7 +79,6 @@ fun LinkChatButton( contentPadding = ZeroPadding, ) { Icon( - tint = Color.White, 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 index 1b493f3a6..01cf7c51c 100644 --- 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 @@ -30,7 +30,6 @@ 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.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat @@ -65,7 +64,6 @@ fun OpenChatButton( contentPadding = ZeroPadding, ) { Icon( - tint = Color.White, 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 index a3cc291e4..a86daef86 100644 --- 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 @@ -31,7 +31,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat @@ -77,7 +76,6 @@ fun ShareChatButton( contentPadding = ZeroPadding, ) { Icon( - tint = Color.White, 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/profile/FollowButtons.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/FollowButtons.kt index 70a5f3d9a..abf7424c5 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 = ButtonBorder, - 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 = ButtonBorder, - 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/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/Theme.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 00594bab8..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,12 +23,10 @@ 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 @@ -37,7 +35,6 @@ 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 @@ -57,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 @@ -70,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), ) @@ -80,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), ) @@ -540,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() } - } - } - } -} From b35fd24a93ca71c6edbaa64b7bd9aac2ac6bc270 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 26 Mar 2025 11:22:38 -0400 Subject: [PATCH 09/12] Fixes QR Code screen padding --- .../ui/screen/loggedIn/qrcode/ShowQRDialog.kt | 188 ++++++++++-------- 1 file changed, 110 insertions(+), 78 deletions(-) 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 074281646..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 @@ -23,16 +23,23 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode 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.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 @@ -53,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 @@ -61,6 +69,7 @@ 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 @@ -95,6 +104,7 @@ fun BackButton(onPress: () -> Unit) { } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ShowQRDialog( user: User, @@ -108,91 +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 = 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, + 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) + } } } } From 29d046a094aaed1e131a9f3bf6213afe7fb74a02 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 26 Mar 2025 12:36:38 -0400 Subject: [PATCH 10/12] Updates zoomable, vico, firebase, jackson and jna --- gradle/libs.versions.toml | 10 +++++----- quartz/build.gradle | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) 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" } } From 8a6b71e9130e0c4eb35474f9fa375165a9623eb6 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 27 Mar 2025 17:39:10 -0400 Subject: [PATCH 11/12] Fixing google services.json for benchmark app --- amethyst/google-services.json | 60 +++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) 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" From c24c23f0038baeaf73b54c5fb9833b026dd0e437 Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Thu, 27 Mar 2025 21:41:53 +0000 Subject: [PATCH 12/12] New Crowdin translations by GitHub Action --- amethyst/src/main/res/values-pl-rPL/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) 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