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 7f842b1a1b..745ba31820 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 @@ -207,6 +207,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.PollsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.results.PollResultsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.privacy.PrivacyOptionsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.products.ProductsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.ProfileScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.payment.SendPaymentScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats.PublicChatsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ShowQRScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.redirect.LoadRedirectScreen @@ -232,11 +234,14 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.HomeTabsSe import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.MessagesSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.MutedThreadsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.NIP47SetupScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.NamecoinSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.NowPlayingSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.OtsSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.ProfileUiSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.ReactionsSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SearchEngineSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SecurityFiltersScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SpammingUsersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.UpdateZapAmountScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.UserSettingsScreen @@ -245,6 +250,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.Cord import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CordnHubScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CordnKeyPackagesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CordnLinkScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.Nip46ConnectedAppsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.Nip46SignerScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.shorts.ShortsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.SoftwareAppDetailScreen @@ -328,19 +334,13 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.NappletsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastEpisodeScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.payment.SendPaymentScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ScanQrImageScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip86.RelayManagementScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.scheduledposts.ScheduledPostsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NamecoinSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NotificationSettingsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.OtsSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ResourceUsageScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnBackupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnMigrateScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46ConnectedAppsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.hls.NewHlsVideoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.webBookmarks.WebBookmarksScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.NewWorkoutScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PayViaIntent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PayViaIntent.kt new file mode 100644 index 0000000000..ec40b3c242 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/PayViaIntent.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025 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.note + +import android.content.Context +import android.content.Intent +import androidx.core.net.toUri +import kotlin.coroutines.cancellation.CancellationException + +fun payViaIntent( + invoice: String, + context: Context, + noWalletFound: String, + onPaid: () -> Unit, + onError: (String) -> Unit, +) { + try { + val intent = Intent(Intent.ACTION_VIEW, "lightning:$invoice".toUri()) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + + context.startActivity(intent) + onPaid() + } catch (e: Exception) { + if (e is CancellationException) throw e + // don't display ugly error messages + // if (e.message != null) { + // onError(stringRes(Res.string.no_wallet_found_with_error, e.message!!)) + // } else { + onError(noWalletFound) + // } + } +} + +/** + * Hands a reusable BOLT12 offer (`lno1…`, from a recipient's kind:10058) off to an + * installed wallet. Unlike a BOLT11 invoice, a BOLT12 offer is NOT a `lightning:` + * payload — that scheme is defined for `lnbc…` invoices. Offers travel as the `lno` + * parameter of a BIP21/BIP321 bitcoin URI (`bitcoin:?lno=lno1…`), where the on-chain + * address is optional so a Lightning-only offer stands on its own. The wallet resolves + * the offer, collects the amount, and completes the payment; this is a plain intent, + * not a NIP-57/NIP-B1 zap, so it produces no Nostr receipt. + */ +fun payViaBolt12Intent( + offer: String, + context: Context, + noWalletFound: String, + onPaid: () -> Unit, + onError: (String) -> Unit, +) { + try { + val intent = Intent(Intent.ACTION_VIEW, "bitcoin:?lno=$offer".toUri()) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + + context.startActivity(intent) + onPaid() + } catch (e: Exception) { + if (e is CancellationException) throw e + onError(noWalletFound) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt index 38be6838c9..e4e543f51e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ReactionsRow.kt @@ -192,6 +192,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.elements.ShareOptionsBottomShe import com.vitorpamplona.amethyst.commons.ui.note.platform.MAX_VOICE_RECORD_SECONDS import com.vitorpamplona.amethyst.commons.ui.note.types.EditState import com.vitorpamplona.amethyst.commons.ui.richtext.InLineIconRenderer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.paymentTargetStyleFor import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.OnchainZapSendDialog import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.navigateToReloadMint import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -232,7 +233,6 @@ import com.vitorpamplona.amethyst.model.zap.RailCapabilityResolver import com.vitorpamplona.amethyst.service.payments.PayToAppAvailability import com.vitorpamplona.amethyst.ui.actions.uploads.RecordAudioBox import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.PaymentTargetsDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.paymentTargetStyleFor import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt index f717e04ab5..e66c6b67a3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ZapCustomDialog.kt @@ -20,8 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note -import android.content.Context -import android.content.Intent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi @@ -58,7 +56,6 @@ 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 import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -68,7 +65,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties -import androidx.core.net.toUri import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel @@ -118,12 +114,14 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55dp import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.ZeroPadding import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText +import com.vitorpamplona.amethyst.commons.ui.wallet.payInvoice +import com.vitorpamplona.amethyst.commons.ui.wallet.rememberWalletAppLauncher import com.vitorpamplona.amethyst.commons.util.showAmount import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent +import com.vitorpamplona.quartz.utils.BigDecimal import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.CancellationException class ZapOptionViewModel : ViewModel() { private var account: Account? = null @@ -266,7 +264,7 @@ fun ZapCustomDialog( onClick = { postViewModel.customAmount = TextFieldValue(amount.toString()) }, - label = { Text("⚡ ${showAmount(amount.toBigDecimal())}") }, + label = { Text("⚡ ${showAmount(BigDecimal(amount))}") }, ) } } @@ -517,7 +515,7 @@ fun DisplayPayable( } Row { Text( - text = showAmount((payable.amountMilliSats / 1000.0f).toBigDecimal()), + text = showAmount(BigDecimal((payable.amountMilliSats / 1000.0f).toString())), maxLines = 1, overflow = TextOverflow.Ellipsis, fontWeight = FontWeight.Bold, @@ -535,10 +533,10 @@ fun DisplayPayable( } Spacer(modifier = DoubleHorzSpacer) - val context = LocalContext.current + val walletLauncher = rememberWalletAppLauncher() PayButton(isActive = !paid.value) { - payViaIntent(payable.invoice, context, noWalletFoundStr, { paid.value = true }) { + walletLauncher.payInvoice(payable.invoice, noWalletFoundStr, { paid.value = true }) { accountViewModel.toastManager.toast( Res.string.error_dialog_zap_error, UserBasedErrorMessage(it, payable.info.user), @@ -548,58 +546,6 @@ fun DisplayPayable( } } -fun payViaIntent( - invoice: String, - context: Context, - noWalletFound: String, - onPaid: () -> Unit, - onError: (String) -> Unit, -) { - try { - val intent = Intent(Intent.ACTION_VIEW, "lightning:$invoice".toUri()) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - - context.startActivity(intent) - onPaid() - } catch (e: Exception) { - if (e is CancellationException) throw e - // don't display ugly error messages - // if (e.message != null) { - // onError(stringRes(Res.string.no_wallet_found_with_error, e.message!!)) - // } else { - onError(noWalletFound) - // } - } -} - -/** - * Hands a reusable BOLT12 offer (`lno1…`, from a recipient's kind:10058) off to an - * installed wallet. Unlike a BOLT11 invoice, a BOLT12 offer is NOT a `lightning:` - * payload — that scheme is defined for `lnbc…` invoices. Offers travel as the `lno` - * parameter of a BIP21/BIP321 bitcoin URI (`bitcoin:?lno=lno1…`), where the on-chain - * address is optional so a Lightning-only offer stands on its own. The wallet resolves - * the offer, collects the amount, and completes the payment; this is a plain intent, - * not a NIP-57/NIP-B1 zap, so it produces no Nostr receipt. - */ -fun payViaBolt12Intent( - offer: String, - context: Context, - noWalletFound: String, - onPaid: () -> Unit, - onError: (String) -> Unit, -) { - try { - val intent = Intent(Intent.ACTION_VIEW, "bitcoin:?lno=$offer".toUri()) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - - context.startActivity(intent) - onPaid() - } catch (e: Exception) { - if (e is CancellationException) throw e - onError(noWalletFound) - } -} - @Composable fun PayButton( isActive: Boolean, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/invoice/ClinkOfferPreview.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/invoice/ClinkOfferPreview.kt index fb26066669..78af498a0d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/invoice/ClinkOfferPreview.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/invoice/ClinkOfferPreview.kt @@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.commons.resources.clink_requesting_invoice import com.vitorpamplona.amethyst.commons.resources.error_dialog_pay_invoice_error import com.vitorpamplona.amethyst.commons.resources.pay import com.vitorpamplona.amethyst.commons.resources.sats +import com.vitorpamplona.amethyst.commons.service.ClinkOfferPayer import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor @@ -71,7 +72,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size25dp import com.vitorpamplona.amethyst.commons.ui.theme.SmallBorder import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.ClinkOfferPayer import com.vitorpamplona.amethyst.ui.components.PaymentCard import com.vitorpamplona.amethyst.ui.components.PaymentCardAmount import com.vitorpamplona.quartz.experimental.clink.common.SatRange diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppPlatform.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppPlatform.kt index 5be307c587..6a853148e5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppPlatform.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppPlatform.kt @@ -38,8 +38,10 @@ import com.vitorpamplona.amethyst.commons.ui.settings.SettingsCategory import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.amethyst.ui.navigation.topbars.AndroidAroundMeLocationLabel +import com.vitorpamplona.amethyst.ui.note.DrawPlayName import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer +import kotlinx.collections.immutable.ImmutableMap import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.AppBottomBar as AppBottomBarImpl import com.vitorpamplona.amethyst.favorites.rememberManifestIconModel as AppRememberManifestIconModel import com.vitorpamplona.amethyst.favorites.rememberNappletIconModel as AppRememberNappletIconModel @@ -60,6 +62,15 @@ object AndroidAppPlatform : AppPlatform { // Per flavour: Play links the hosted policies, F-Droid surfaces none. override fun legalSettingsCategory(uriHandler: UriHandler): SettingsCategory? = flavorLegalSettingsCategory(uriHandler) + @Composable + override fun SpeakNameButton(name: String) = DrawPlayName(name) + + @Composable + override fun rememberAppLanguages(): ImmutableMap { + val context = LocalContext.current + return remember(context) { context.getLangPreferenceDropdownEntries() } + } + @Composable override fun AppBottomBar( selectedRoute: Route?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppServices.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppServices.kt index f6f1375a9e..2c072abaa1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppServices.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppServices.kt @@ -23,18 +23,26 @@ package com.vitorpamplona.amethyst.ui.platform import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.browser.BrowserHistoryRegistry import com.vitorpamplona.amethyst.commons.browser.BrowserIconRegistry +import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46ClientStore import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionStore import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppsRegistry import com.vitorpamplona.amethyst.commons.model.location.DeviceLocation import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores +import com.vitorpamplona.amethyst.commons.model.preferences.NamecoinSettingsStore +import com.vitorpamplona.amethyst.commons.model.preferences.OtsSettingsStore import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionLedger import com.vitorpamplona.amethyst.commons.service.AppServices import com.vitorpamplona.amethyst.commons.service.BlossomServerFinder import com.vitorpamplona.amethyst.commons.service.ai.AltTextSuggester +import com.vitorpamplona.amethyst.commons.service.namecoin.NamecoinClients import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow import com.vitorpamplona.amethyst.service.ai.MLKitImageLabelService import com.vitorpamplona.amethyst.service.location.CachedReversedGeoLocations +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinCoreRpcConfig import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.RpcProbeResult +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ServerTestResult import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -64,6 +72,14 @@ object AndroidAppServices : AppServices { override val namecoinResolver: NamecoinNameResolver get() = Amethyst.instance.namecoinResolver + override val namecoinSettings: NamecoinSettingsStore get() = Amethyst.instance.namecoinPrefs + + override val namecoinClients: NamecoinClients = AndroidNamecoinClients + + override val otsSettings: OtsSettingsStore get() = Amethyst.instance.otsPrefs + + override val nip46ClientStore: Nip46ClientStore get() = Amethyst.instance.nip46ClientStore + override val blossomServerFinder: BlossomServerFinder = AndroidBlossomServerFinder override val deviceLocation: DeviceLocation get() = Amethyst.instance.locationManager @@ -85,3 +101,19 @@ private object AndroidBlossomServerFinder : BlossomServerFinder { .findServers(blossomUri) ?.serverUrl } + +/** The app's ElectrumX and Namecoin Core clients, built lazily by [Amethyst.instance]. */ +private object AndroidNamecoinClients : NamecoinClients { + override suspend fun testElectrumxServer(server: ElectrumxServer): ServerTestResult = Amethyst.instance.electrumXClient.testServer(server) + + override suspend fun probeCoreRpc(config: NamecoinCoreRpcConfig): RpcProbeResult = Amethyst.instance.namecoinCoreRpcClient.probe(config) + + override fun addPinnedCert(pem: String) { + Amethyst.instance.electrumXClient.addPinnedCert(pem) + Amethyst.instance.namecoinCoreRpcClient.addPinnedCert(pem) + } + + override fun setCoreRpcConfig(config: NamecoinCoreRpcConfig) { + Amethyst.instance.namecoinCoreRpcClient.setConfig(config) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AppLanguages.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AppLanguages.kt new file mode 100644 index 0000000000..b8bc731dd4 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AppLanguages.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2025 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.platform + +import android.content.Context +import androidx.core.os.LocaleListCompat +import com.vitorpamplona.amethyst.R +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.toImmutableMap +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import java.io.IOException + +private fun Context.getLocaleListFromXml(): LocaleListCompat { + val tagsList = mutableListOf() + try { + val xpp: XmlPullParser = resources.getXml(R.xml.locales_config) + while (xpp.eventType != XmlPullParser.END_DOCUMENT) { + if (xpp.eventType == XmlPullParser.START_TAG && xpp.name == "locale") { + tagsList.add(xpp.getAttributeValue(0)) + } + xpp.next() + } + } catch (e: XmlPullParserException) { + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + + return LocaleListCompat.forLanguageTags(tagsList.joinToString(",")) +} + +fun Context.getLangPreferenceDropdownEntries(): ImmutableMap { + val localeList = getLocaleListFromXml() + val map = mutableMapOf() + + for (a in 0 until localeList.size()) { + localeList[a].let { + map.put( + it!!.getDisplayName(it).replaceFirstChar { char -> char.uppercase() }, + it.toLanguageTag(), + ) + } + } + return map.toImmutableMap() +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppDetailScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppDetailScreen.kt index 16ebfbc601..b5d6307e7d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppDetailScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppDetailScreen.kt @@ -126,6 +126,12 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.platform.rememberManifestIconModel import com.vitorpamplona.amethyst.commons.ui.platform.rememberWebAppIconModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.Nip46AppIcon +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.Nip46LiveStatus +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.Nip46ReconnectPill +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.Nip46StatusDot +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.nip46AppOnline +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46.nip46ClientSubtitle import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.napplet.NappletBrokerService @@ -134,12 +140,6 @@ import com.vitorpamplona.amethyst.napplet.counterpartyLabel import com.vitorpamplona.amethyst.napplet.descriptionRes import com.vitorpamplona.amethyst.napplet.labelRes import com.vitorpamplona.amethyst.napplet.resolveNappletMeta -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46AppIcon -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46LiveStatus -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46ReconnectPill -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46StatusDot -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.nip46AppOnline -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.nip46ClientSubtitle import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/PaymentTargetsDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/PaymentTargetsDialog.kt index bf49de7a0a..f4749c8ced 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/PaymentTargetsDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/PaymentTargetsDialog.kt @@ -66,6 +66,8 @@ import com.vitorpamplona.amethyst.commons.ui.components.M3ActionSection import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.ErrorMessageDialog +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.PaymentTargetPill +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.paymentTargetUri import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/TabPinnedNotes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/TabPinnedNotes.kt index b31a6fa4ab..875fea0b05 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/TabPinnedNotes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/TabPinnedNotes.kt @@ -29,8 +29,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel @Composable fun TabPinnedNotes( diff --git a/amethyst/src/main/res/drawable/github.xml b/amethyst/src/main/res/drawable/github.xml deleted file mode 100644 index e3e4918508..0000000000 --- a/amethyst/src/main/res/drawable/github.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/commons/plans/2026-09-27-one-ui-android-desktop.md b/commons/plans/2026-09-27-one-ui-android-desktop.md index 891b398efc..a406d77da4 100644 --- a/commons/plans/2026-09-27-one-ui-android-desktop.md +++ b/commons/plans/2026-09-27-one-ui-android-desktop.md @@ -477,7 +477,16 @@ assemblers, `EventSync`, …). Packages are renamed on the way: voice reply, the calendar collections and event detail, the geocache detail and editor, the live-activity channel, chess, Cordn group creation, the QR share and the compose settings. 204 of 252 screens shared. - - **Next:** 48 screens remain, and what blocks them is the platform itself: + - **Wave 11 (2026-10-04):** Profile and settings. `AppServices` gained `namecoinSettings`, + `namecoinClients` (a port over the JVM-only ElectrumX and Core RPC clients), `otsSettings` + and `nip46ClientStore`. `AppPlatform` gained `rememberAppLanguages` (Android reads + `locales_config.xml`) and `SpeakNameButton` (text-to-speech). `OtsSettings` and its store are + in commonMain, on quartz's new `BitcoinExplorerUrls`; `RpcProbeResult` is too. + `deviceDescription()` is an expect in commons. Copy-to-clipboard goes through the shared + `Clipboard.setText`; the brand icons for identity claims are Compose resources. Moved: the + profile (60 files: header, tabs, payment sheet), the app, NIP-46, Namecoin and OTS settings. + 210 of 252 screens shared. + - **Next:** 42 screens remain, and what blocks them is the platform itself: - `ReactionsRow` (2.7k lines: pay-to-app, voice replies, wallet intents). - The Nests activity and PiP. - The call screens. diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.android.kt b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.android.kt new file mode 100644 index 0000000000..a07ed59b10 --- /dev/null +++ b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.android.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 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.commons.util + +import android.os.Build + +actual fun deviceDescription(): String = "${Build.MANUFACTURER} ${Build.MODEL}, Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})" diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/nip03Timestamp/OtsSettings.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip03Timestamp/OtsSettings.kt similarity index 91% rename from commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/nip03Timestamp/OtsSettings.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip03Timestamp/OtsSettings.kt index 10a31c0afe..fa0415cb6b 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/nip03Timestamp/OtsSettings.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip03Timestamp/OtsSettings.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.commons.model.nip03Timestamp import androidx.compose.runtime.Stable -import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpBitcoinExplorer +import com.vitorpamplona.quartz.nip03Timestamp.BitcoinExplorerUrls import kotlinx.serialization.Serializable /** @@ -57,8 +57,8 @@ data class OtsSettings( val KNOWN_EXPLORERS = listOf( - OkHttpBitcoinExplorer.MEMPOOL_API_URL to "mempool.space (Tor-friendly)", - OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL to "blockstream.info", + BitcoinExplorerUrls.MEMPOOL to "mempool.space (Tor-friendly)", + BitcoinExplorerUrls.BLOCKSTREAM to "blockstream.info", ) fun isValidUrl(url: String): Boolean { diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/OtsSettingsStore.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/OtsSettingsStore.kt similarity index 100% rename from commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/OtsSettingsStore.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/preferences/OtsSettingsStore.kt diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/AppServices.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/AppServices.kt index ec851b8f1e..63262cb6bd 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/AppServices.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/AppServices.kt @@ -22,12 +22,16 @@ package com.vitorpamplona.amethyst.commons.service import com.vitorpamplona.amethyst.commons.browser.BrowserHistoryRegistry import com.vitorpamplona.amethyst.commons.browser.BrowserIconRegistry +import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46ClientStore import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionStore import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppsRegistry import com.vitorpamplona.amethyst.commons.model.location.DeviceLocation import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores +import com.vitorpamplona.amethyst.commons.model.preferences.NamecoinSettingsStore +import com.vitorpamplona.amethyst.commons.model.preferences.OtsSettingsStore import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionLedger import com.vitorpamplona.amethyst.commons.service.ai.AltTextSuggester +import com.vitorpamplona.amethyst.commons.service.namecoin.NamecoinClients import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver import kotlinx.coroutines.flow.Flow @@ -64,6 +68,18 @@ interface AppServices { /** Resolves `.bit` names and `d/`/`id/` identifiers over the configured ElectrumX servers. */ val namecoinResolver: NamecoinNameResolver + /** The Namecoin backend settings (servers, Core RPC, pinned certificates). */ + val namecoinSettings: NamecoinSettingsStore + + /** The live Namecoin clients the settings screen tests and reconfigures. */ + val namecoinClients: NamecoinClients get() = NamecoinClients.None + + /** The OpenTimestamps blockchain explorer setting. */ + val otsSettings: OtsSettingsStore + + /** The NIP-46 remote-signer clients this device has connected to. */ + val nip46ClientStore: Nip46ClientStore + /** Finds the server behind a `blossom:` URI (BUD-10). */ val blossomServerFinder: BlossomServerFinder get() = BlossomServerFinder.None diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/namecoin/NamecoinClients.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/namecoin/NamecoinClients.kt new file mode 100644 index 0000000000..56830d861d --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/namecoin/NamecoinClients.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025 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.commons.service.namecoin + +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinCoreRpcConfig +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.RpcProbeResult +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ServerTestResult + +/** + * What the Namecoin settings screen asks of the live clients: test a server, probe a Namecoin + * Core node, and hand them a pinned certificate or a new Core RPC config. The ElectrumX and Core + * RPC clients themselves are JVM-only (raw TLS sockets, OkHttp), so the screen reaches them here. + */ +interface NamecoinClients { + suspend fun testElectrumxServer(server: ElectrumxServer): ServerTestResult + + suspend fun probeCoreRpc(config: NamecoinCoreRpcConfig): RpcProbeResult + + /** Pins a self-signed certificate on both backends, so the user confirms it once. */ + fun addPinnedCert(pem: String) + + fun setCoreRpcConfig(config: NamecoinCoreRpcConfig) + + /** No clients: every test reports that nothing is available. */ + object None : NamecoinClients { + override suspend fun testElectrumxServer(server: ElectrumxServer): ServerTestResult = ServerTestResult(server = server, success = false, responseTimeMs = 0, error = "Namecoin is not available on this platform") + + override suspend fun probeCoreRpc(config: NamecoinCoreRpcConfig): RpcProbeResult = RpcProbeResult(success = false, elapsedMs = 0, error = "Namecoin is not available on this platform") + + override fun addPinnedCert(pem: String) {} + + override fun setCoreRpcConfig(config: NamecoinCoreRpcConfig) {} + } +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.kt new file mode 100644 index 0000000000..e3e61bffc2 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.kt @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 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.commons.util + +/** A one-line description of this device and OS, for diagnostic screens: "Google Pixel 8, Android 15 (API 35)". */ +expect fun deviceDescription(): String diff --git a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.ios.kt new file mode 100644 index 0000000000..efa5d89dc3 --- /dev/null +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.ios.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 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.commons.util + +import platform.UIKit.UIDevice + +actual fun deviceDescription(): String { + val device = UIDevice.currentDevice + return "${device.model}, ${device.systemName} ${device.systemVersion}" +} diff --git a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.jvm.kt new file mode 100644 index 0000000000..305199e0a5 --- /dev/null +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/util/DeviceDescription.jvm.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 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.commons.util + +actual fun deviceDescription(): String = "${System.getProperty("os.name")} ${System.getProperty("os.version")} (${System.getProperty("os.arch")}), Java ${System.getProperty("java.version")}" diff --git a/amethyst/src/main/res/drawable/mastodon.xml b/commonsUI/src/commonMain/composeResources/drawable/mastodon.xml similarity index 100% rename from amethyst/src/main/res/drawable/mastodon.xml rename to commonsUI/src/commonMain/composeResources/drawable/mastodon.xml diff --git a/amethyst/src/main/res/drawable/telegram.xml b/commonsUI/src/commonMain/composeResources/drawable/telegram.xml similarity index 100% rename from amethyst/src/main/res/drawable/telegram.xml rename to commonsUI/src/commonMain/composeResources/drawable/telegram.xml diff --git a/amethyst/src/main/res/drawable/x.xml b/commonsUI/src/commonMain/composeResources/drawable/x.xml similarity index 100% rename from amethyst/src/main/res/drawable/x.xml rename to commonsUI/src/commonMain/composeResources/drawable/x.xml diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ClinkOfferPayer.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/ClinkOfferPayer.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/ClinkOfferPayer.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/ClinkOfferPayer.kt index d317102733..705745ff7f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ClinkOfferPayer.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/ClinkOfferPayer.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.service +package com.vitorpamplona.amethyst.commons.service import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.tor.MoneyOpRelayRouting @@ -36,6 +36,7 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/util/LongPressCopyText.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/util/LongPressCopyText.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/util/LongPressCopyText.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/util/LongPressCopyText.kt index bb58c01144..a3f52111f3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/util/LongPressCopyText.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/util/LongPressCopyText.kt @@ -18,9 +18,8 @@ * 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.components.util +package com.vitorpamplona.amethyst.commons.ui.components.util -import android.widget.Toast import androidx.compose.foundation.combinedClickable import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme @@ -30,12 +29,12 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.copied_to_clipboard import com.vitorpamplona.amethyst.commons.resources.copy_to_clipboard +import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -44,7 +43,7 @@ import org.jetbrains.compose.resources.StringResource /** * Text composable that opens [onClick] on a tap and copies [copyValue] to the - * system clipboard on a long-press (with a Toast confirmation). + * system clipboard on a long-press (with a short notice confirming it). * * This is the long-press-to-copy counterpart of [com.vitorpamplona.amethyst.commons.ui.components.ClickableTextPrimary]. * It deliberately uses a plain [Text] + [combinedClickable] outer modifier @@ -58,7 +57,7 @@ import org.jetbrains.compose.resources.StringResource * @param copyValue raw value placed on the clipboard on long-press * (typically the full, unmodified profile field). * @param onClick tap action — usually "open the link" or "expand zap UI". - * @param toastResId string resource shown via [Toast.LENGTH_SHORT] after the + * @param toastResId string resource shown as a short notice after the * value is placed on the clipboard. Defaults to a generic * "Copied to clipboard" message. * @param onLongClickLabelResId accessibility label exposed to TalkBack for @@ -78,7 +77,7 @@ fun LongPressCopyText( toastResId: StringResource = Res.string.copied_to_clipboard, onLongClickLabelResId: StringResource = Res.string.copy_to_clipboard, ) { - val context = LocalContext.current + val shortNotice = rememberShortNotice() val clipboard = LocalClipboard.current val scope = rememberCoroutineScope() val longClickLabel = stringRes(onLongClickLabelResId) @@ -96,7 +95,7 @@ fun LongPressCopyText( onLongClick = { scope.launch { clipboard.setText(copyValue) - Toast.makeText(context, loadStringRes(toastResId), Toast.LENGTH_SHORT).show() + shortNotice.show(loadStringRes(toastResId)) } }, onLongClickLabel = longClickLabel, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/AppPlatform.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/AppPlatform.kt index 3df0c16cbb..5d97f68681 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/AppPlatform.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/AppPlatform.kt @@ -32,6 +32,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ScanOutcome import com.vitorpamplona.amethyst.commons.ui.settings.SettingsCategory import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentMapOf /** * The app shell's pieces that shared screens embed but only the front end can draw yet: the @@ -47,6 +49,13 @@ interface AppPlatform { /** The settings screen's legal links, when the build's distribution calls for them. */ fun legalSettingsCategory(uriHandler: UriHandler): SettingsCategory? = null + /** + * The languages the app is translated into, as display name ("Deutsch") to language tag + * ("de"), for the language picker. Empty where the platform follows the system language only. + */ + @Composable + fun rememberAppLanguages(): ImmutableMap = persistentMapOf() + @Composable fun AppBottomBar( selectedRoute: Route?, @@ -58,6 +67,10 @@ interface AppPlatform { @Composable fun AroundMeLocationLabel() {} + /** A button that reads [name] aloud with the platform's text-to-speech; nothing where there is none. */ + @Composable + fun SpeakNameButton(name: String) {} + /** A full-screen dialog to pick a place (map, search, "use my location"), as a geohash. */ @Composable fun GeohashLocationPickerDialog( @@ -135,6 +148,9 @@ fun AppBottomBar( @Composable fun AroundMeLocationLabel() = LocalAppPlatform.current.AroundMeLocationLabel() +@Composable +fun SpeakNameButton(name: String) = LocalAppPlatform.current.SpeakNameButton(name) + @Composable fun GeohashLocationPickerDialog( initialGeohash: String?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/ProfileScreen.kt similarity index 87% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/ProfileScreen.kt index e318fed286..9b5d9615bd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/ProfileScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/ProfileScreen.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.screen.loggedIn.profile +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile import androidx.compose.foundation.ScrollState import androidx.compose.foundation.gestures.scrollBy @@ -63,7 +63,6 @@ import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntSize import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.navigation.Route @@ -76,47 +75,48 @@ import com.vitorpamplona.amethyst.commons.resources.mutual import com.vitorpamplona.amethyst.commons.resources.notes import com.vitorpamplona.amethyst.commons.resources.profile_tab_apps import com.vitorpamplona.amethyst.commons.resources.replies +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.UserBlockedFeed import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.apps.TabApps +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.apps.dal.UserProfileAppsFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks.BookmarkTabHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks.TabBookmarks +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks.dal.UserProfileBookmarksFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.conversations.TabNotesConversations +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.conversations.dal.UserProfileConversationsFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers.FollowersTabHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers.TabFollowers +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers.dal.UserProfileFollowersUserFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows.FollowTabHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows.TabFollows +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows.dal.UserProfileFollowsUserFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.TabGallery +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.dal.UserProfileGalleryFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.hashtags.FollowedTagsTabHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.hashtags.TabFollowedTags +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.ProfileHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.ProfileTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.mutual.TabMutualConversations +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.mutual.dal.UserProfileMutualFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.newthreads.TabNotesNewThreads +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.newthreads.dal.UserProfileNewThreadsFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.relays.TabRelays +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports.ReportsTabHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports.TabReports +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps.TabReceivedZaps +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps.ZapTabHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.Size8dp import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.UserExternalIdentitiesViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.apps.TabApps -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.apps.dal.UserProfileAppsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.BookmarkTabHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.TabBookmarks -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.dal.UserProfileBookmarksFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.conversations.TabNotesConversations -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.conversations.dal.UserProfileConversationsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.followers.FollowersTabHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.followers.TabFollowers -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.followers.dal.UserProfileFollowersUserFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.follows.FollowTabHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.follows.TabFollows -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.follows.dal.UserProfileFollowsUserFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.TabGallery -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.dal.UserProfileGalleryFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.hashtags.FollowedTagsTabHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.hashtags.TabFollowedTags -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.ProfileHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.ProfileTopBar -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.mutual.TabMutualConversations -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.mutual.dal.UserProfileMutualFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.newthreads.TabNotesNewThreads -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.newthreads.dal.UserProfileNewThreadsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.relays.TabRelays -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.ReportsTabHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.TabReports -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.TabReceivedZaps -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.ZapTabHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsViewModel import kotlinx.coroutines.launch @Composable @@ -154,7 +154,7 @@ fun PrepareViewModels( nav: INav, ) { val followsFeedViewModel: UserProfileFollowsUserFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileFollowsUserFeedViewModel", factory = UserProfileFollowsUserFeedViewModel.Factory( @@ -164,7 +164,7 @@ fun PrepareViewModels( ) val galleryFeedViewModel: UserProfileGalleryFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserGalleryFeedViewModel", factory = UserProfileGalleryFeedViewModel.Factory( @@ -174,7 +174,7 @@ fun PrepareViewModels( ) val followersFeedViewModel: UserProfileFollowersUserFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileFollowersUserFeedViewModel", factory = UserProfileFollowersUserFeedViewModel.Factory( @@ -184,26 +184,26 @@ fun PrepareViewModels( ) val appRecommendations: UserAppRecommendationsFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserAppRecommendationsFeedViewModel", factory = UserAppRecommendationsFeedViewModel.Factory(baseUser), ) val externalIdentities: UserExternalIdentitiesViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserExternalIdentitiesViewModel", factory = UserExternalIdentitiesViewModel.Factory(baseUser), ) val zapFeedViewModel: UserProfileZapsViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileZapsFeedViewModel", factory = UserProfileZapsViewModel.Factory(baseUser, accountViewModel.account), ) val threadsViewModel: UserProfileNewThreadsFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileNewThreadsFeedViewModel", factory = UserProfileNewThreadsFeedViewModel.Factory( @@ -213,7 +213,7 @@ fun PrepareViewModels( ) val repliesViewModel: UserProfileConversationsFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileConversationsFeedViewModel", factory = UserProfileConversationsFeedViewModel.Factory( @@ -223,7 +223,7 @@ fun PrepareViewModels( ) val mutualViewModel: UserProfileMutualFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileMutualFeedViewModel", factory = UserProfileMutualFeedViewModel.Factory( @@ -233,7 +233,7 @@ fun PrepareViewModels( ) val bookmarksFeedViewModel: UserProfileBookmarksFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileBookmarksFeedViewModel", factory = UserProfileBookmarksFeedViewModel.Factory( @@ -243,7 +243,7 @@ fun PrepareViewModels( ) val pinnedNotesFeedViewModel: UserProfilePinnedNotesFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfilePinnedNotesFeedViewModel", factory = UserProfilePinnedNotesFeedViewModel.Factory( @@ -253,7 +253,7 @@ fun PrepareViewModels( ) val reportsFeedViewModel: UserProfileReportFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileReportFeedViewModel", factory = UserProfileReportFeedViewModel.Factory( @@ -263,7 +263,7 @@ fun PrepareViewModels( ) val appsFeedViewModel: UserProfileAppsFeedViewModel = - viewModel( + rememberViewModel( key = baseUser.pubkeyHex + "UserProfileAppsFeedViewModel", factory = UserProfileAppsFeedViewModel.Factory( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/TabApps.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/TabApps.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/TabApps.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/TabApps.kt index b4fb0f1c65..26cbbc9179 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/TabApps.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/TabApps.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.screen.loggedIn.profile.apps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.apps import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -29,8 +29,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.apps.dal.UserProfileAppsFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.apps.dal.UserProfileAppsFeedViewModel /** * Profile tab listing the user's published nsites and napplets. Each row is the inert feed card; diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedFilter.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedFilter.kt index 0dc7eb64e0..0536e704e3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedFilter.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.screen.loggedIn.profile.apps.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.apps.dal import com.vitorpamplona.amethyst.commons.feeds.AdditiveFeedFilter import com.vitorpamplona.amethyst.commons.feeds.FilterByListParams diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedViewModel.kt index 1f1c2c4ff0..0aff456251 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/apps/dal/UserProfileAppsFeedViewModel.kt @@ -18,14 +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.screen.loggedIn.profile.apps.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.apps.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserProfileAppsFeedViewModel( @@ -37,6 +39,9 @@ class UserProfileAppsFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileAppsFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileAppsFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt index a57e2a42c0..237e104323 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/BookmarkTabHeader.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.screen.loggedIn.profile.bookmarks +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks import androidx.compose.material3.Text import androidx.compose.runtime.Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/TabBookmarks.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/TabBookmarks.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/TabBookmarks.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/TabBookmarks.kt index 3307c80418..47f310320d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/TabBookmarks.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/TabBookmarks.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.screen.loggedIn.profile.bookmarks +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -29,8 +29,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks.dal.UserProfileBookmarksFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.bookmarks.dal.UserProfileBookmarksFeedViewModel @Composable fun TabBookmarks( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedFilter.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedFilter.kt index b31eb14984..ab16c1fe06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedFilter.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.screen.loggedIn.profile.bookmarks.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks.dal import com.vitorpamplona.amethyst.commons.feeds.FeedFilter import com.vitorpamplona.amethyst.commons.model.Account diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedViewModel.kt index fc7dc200df..07a62ef47a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/bookmarks/dal/UserProfileBookmarksFeedViewModel.kt @@ -18,14 +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.screen.loggedIn.profile.bookmarks.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.bookmarks.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserProfileBookmarksFeedViewModel( @@ -37,6 +39,9 @@ class UserProfileBookmarksFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileBookmarksFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileBookmarksFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/TabNotesConversations.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/TabNotesConversations.kt similarity index 90% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/TabNotesConversations.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/TabNotesConversations.kt index bc7451de42..cf70f6a222 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/TabNotesConversations.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/TabNotesConversations.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.screen.loggedIn.profile.conversations +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.conversations import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -26,8 +26,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.conversations.dal.UserProfileConversationsFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.conversations.dal.UserProfileConversationsFeedViewModel @Composable fun TabNotesConversations( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedFilter.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedFilter.kt index 5390218eea..0623c09b98 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedFilter.kt @@ -20,7 +20,7 @@ */ @file:Suppress("DEPRECATION") -package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.conversations.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.conversations.dal import com.vitorpamplona.amethyst.commons.feeds.AdditiveFeedFilter import com.vitorpamplona.amethyst.commons.feeds.sortedByDefaultFeedOrder diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedViewModel.kt index 41ebb0f777..2a8891b644 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/conversations/dal/UserProfileConversationsFeedViewModel.kt @@ -18,14 +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.screen.loggedIn.profile.conversations.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.conversations.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserProfileConversationsFeedViewModel( @@ -37,6 +39,9 @@ class UserProfileConversationsFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileConversationsFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileConversationsFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt index 55f17dedb4..c2cdcb08fc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/FollowersTabHeader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/FollowersTabHeader.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.screen.loggedIn.profile.followers +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -28,9 +28,9 @@ import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserAssertionsFollowerCount import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.number_followers +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers.dal.UserProfileFollowersUserFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.followers.dal.UserProfileFollowersUserFeedViewModel @Composable fun FollowersTabHeader( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/TabFollowers.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/TabFollowers.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/TabFollowers.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/TabFollowers.kt index 43b2a11a4b..016918a848 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/TabFollowers.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/TabFollowers.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.screen.loggedIn.profile.followers +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -33,10 +33,10 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.UserCompose +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers.dal.UserProfileFollowersUserFeedViewModel import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.followers.dal.UserProfileFollowersUserFeedViewModel @Composable fun TabFollowers( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/dal/UserProfileFollowersUserFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/dal/UserProfileFollowersUserFeedViewModel.kt similarity index 90% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/dal/UserProfileFollowersUserFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/dal/UserProfileFollowersUserFeedViewModel.kt index 78a45c1a61..d2c888fa9b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/followers/dal/UserProfileFollowersUserFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/followers/dal/UserProfileFollowersUserFeedViewModel.kt @@ -18,24 +18,27 @@ * 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.profile.followers.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.followers.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.stateIn +import kotlin.reflect.KClass @Stable class UserProfileFollowersUserFeedViewModel( @@ -92,6 +95,9 @@ class UserProfileFollowersUserFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileFollowersUserFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileFollowersUserFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt index 6d07f2ece2..2358c5101b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/FollowTabHeader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/FollowTabHeader.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.screen.loggedIn.profile.follows +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -26,9 +26,9 @@ import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.number_following +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows.dal.UserProfileFollowsUserFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.follows.dal.UserProfileFollowsUserFeedViewModel @Composable fun FollowTabHeader( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/TabFollows.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/TabFollows.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/TabFollows.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/TabFollows.kt index 62993d5ae3..2f8c685422 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/TabFollows.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/TabFollows.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.screen.loggedIn.profile.follows +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -33,10 +33,10 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.UserCompose +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows.dal.UserProfileFollowsUserFeedViewModel import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.follows.dal.UserProfileFollowsUserFeedViewModel @Composable fun TabFollows( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/dal/UserProfileFollowsUserFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/dal/UserProfileFollowsUserFeedViewModel.kt similarity index 90% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/dal/UserProfileFollowsUserFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/dal/UserProfileFollowsUserFeedViewModel.kt index 7d14f5fdc4..32c5114e27 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/follows/dal/UserProfileFollowsUserFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/follows/dal/UserProfileFollowsUserFeedViewModel.kt @@ -18,17 +18,19 @@ * 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.profile.follows.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.follows.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn @@ -36,6 +38,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.stateIn +import kotlin.reflect.KClass @Stable class UserProfileFollowsUserFeedViewModel( @@ -89,6 +92,9 @@ class UserProfileFollowsUserFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileFollowsUserFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileFollowsUserFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt index 0b56c3bddb..22876e4349 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/GalleryCardCompose.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.screen.loggedIn.profile.gallery +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/ProfileGalleryFeed.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/ProfileGalleryFeed.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/ProfileGalleryFeed.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/ProfileGalleryFeed.kt index 8407656f48..0741c0ed7d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/ProfileGalleryFeed.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/ProfileGalleryFeed.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.screen.loggedIn.profile.gallery +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement.spacedBy diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/QuickActionGallery.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/QuickActionGallery.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/QuickActionGallery.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/QuickActionGallery.kt index b8df4039c4..7e22107035 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/QuickActionGallery.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/QuickActionGallery.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.screen.loggedIn.profile.gallery +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/TabGallery.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/TabGallery.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/TabGallery.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/TabGallery.kt index 8912863c06..fed721b851 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/TabGallery.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/TabGallery.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.screen.loggedIn.profile.gallery +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -28,8 +28,8 @@ import androidx.compose.ui.Modifier import com.vitorpamplona.amethyst.commons.ui.feeds.ScrollStateKeys import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.SaveableGridFeedState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.dal.UserProfileGalleryFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.dal.UserProfileGalleryFeedViewModel @Composable fun TabGallery( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedFilter.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedFilter.kt index c9b7901193..b4e36c1537 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedFilter.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.screen.loggedIn.profile.gallery.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.dal import com.vitorpamplona.amethyst.commons.feeds.AdditiveFeedFilter import com.vitorpamplona.amethyst.commons.feeds.FilterByListParams diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedViewModel.kt index ed760a427e..9d64ea461f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/gallery/dal/UserProfileGalleryFeedViewModel.kt @@ -18,14 +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.screen.loggedIn.profile.gallery.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.gallery.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserProfileGalleryFeedViewModel( @@ -37,6 +39,9 @@ class UserProfileGalleryFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileGalleryFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileGalleryFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt index 7be9af6d15..29be9a3a4a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/hashtags/FollowedTagsTabHeader.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.screen.loggedIn.profile.hashtags +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.hashtags import androidx.compose.material3.Text import androidx.compose.runtime.Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt index 82091a5d8a..6673c7e375 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.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.screen.loggedIn.profile.hashtags +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.hashtags import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt index ee238b69fd..8213a4a19f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DisplayFollowUnfollowButton.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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayPaymentTargets.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DisplayPaymentTargets.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayPaymentTargets.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DisplayPaymentTargets.kt index 7a8d0a6936..75d50cdfe0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DisplayPaymentTargets.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DisplayPaymentTargets.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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header import androidx.compose.runtime.Composable import androidx.compose.runtime.remember @@ -35,10 +35,10 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.error_dialog_payment_error import com.vitorpamplona.amethyst.commons.resources.no_payment_app_found_for_type import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.payment.ProfilePaymentMethod import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.commons.ui.theme.Size16Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.payment.ProfilePaymentMethod import com.vitorpamplona.quartz.nipA3PaymentTargets.PaymentTarget import com.vitorpamplona.quartz.nipBCOnchainZaps.taproot.SegwitAddress diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt index 872e6aabab..e490479d15 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DrawAdditionalInfo.kt @@ -18,9 +18,8 @@ * 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.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header -import android.content.ClipData import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.Row @@ -40,16 +39,13 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler 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.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.buzz.ui.BuzzAgentLabel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -67,12 +63,19 @@ import com.vitorpamplona.amethyst.commons.resources.show_nprofile_as_a_qr_code import com.vitorpamplona.amethyst.commons.resources.telegram import com.vitorpamplona.amethyst.commons.resources.twitter import com.vitorpamplona.amethyst.commons.resources.website +import com.vitorpamplona.amethyst.commons.resources.x import com.vitorpamplona.amethyst.commons.ui.components.TranslatableRichTextViewer +import com.vitorpamplona.amethyst.commons.ui.components.util.LongPressCopyText +import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.ObserveAndRenderNIP05VerifiedSymbol import com.vitorpamplona.amethyst.commons.ui.note.lastSeenSentence import com.vitorpamplona.amethyst.commons.ui.painterRes +import com.vitorpamplona.amethyst.commons.ui.platform.SpeakNameButton import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.apps.DisplayAppRecommendations +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.badges.DisplayBadges import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size15Modifier import com.vitorpamplona.amethyst.commons.ui.theme.SpacedBy3dp @@ -82,12 +85,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.util.toShortDisplay import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.UserExternalIdentitiesViewModel -import com.vitorpamplona.amethyst.ui.components.util.LongPressCopyText -import com.vitorpamplona.amethyst.ui.note.DrawPlayName -import com.vitorpamplona.amethyst.ui.painterRes -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.DisplayAppRecommendations -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.badges.DisplayBadges import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip39ExtIdentities.GitHubIdentity @@ -96,9 +93,11 @@ import com.vitorpamplona.quartz.nip39ExtIdentities.MastodonIdentity import com.vitorpamplona.quartz.nip39ExtIdentities.TelegramIdentity import com.vitorpamplona.quartz.nip39ExtIdentities.TwitterIdentity import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.StringResource private const val IDENTITY_ICON_CACHE_KEY = 0 @@ -151,7 +150,7 @@ fun DrawAdditionalInfo( Spacer(StdHorzSpacer) } - DrawPlayName(displayName) + SpeakNameButton(displayName) } // A Buzz agent says so, and whose it is: the owner from the NIP-OA tag on its kind 0, @@ -184,7 +183,7 @@ fun DrawAdditionalInfo( modifier = Modifier.size(23.dp).padding(start = 5.dp), onClick = { scope.launch { - clipboard.setClipEntry(ClipEntry(ClipData.newPlainText("npub", baseUser.pubkeyNpub()))) + clipboard.setText(baseUser.pubkeyNpub()) } }, ) { @@ -208,7 +207,7 @@ fun DrawAdditionalInfo( modifier = Modifier.size(23.dp).padding(start = 5.dp), onClick = { scope.launch { - clipboard.setClipEntry(ClipEntry(ClipData.newPlainText("nprofile", baseUser.toNProfile()))) + clipboard.setText(baseUser.toNProfile()) } }, ) { @@ -271,7 +270,7 @@ fun DrawAdditionalInfo( Row(verticalAlignment = Alignment.CenterVertically) { Icon( tint = Color.Unspecified, - painter = painterRes(resourceId = getIdentityClaimIcon(identity), IDENTITY_ICON_CACHE_KEY), + painter = painterRes(getIdentityClaimIcon(identity), IDENTITY_ICON_CACHE_KEY), contentDescription = stringRes(getIdentityClaimDescription(identity)), modifier = Modifier.size(18.dp), ) @@ -341,7 +340,6 @@ fun DisplayLastSeen( val lastSeen by lastSeenFlow.collectAsStateWithLifecycle(null) lastSeen?.let { timestamp -> - val context = LocalContext.current Text( text = lastSeenSentence(timestamp), color = MaterialTheme.colorScheme.placeholderText, @@ -384,13 +382,13 @@ fun DisplayNip05ProfileStatus( } } -fun getIdentityClaimIcon(identity: IdentityClaimTag): Int = +fun getIdentityClaimIcon(identity: IdentityClaimTag): DrawableResource = when (identity) { - is TwitterIdentity -> R.drawable.x - is TelegramIdentity -> R.drawable.telegram - is MastodonIdentity -> R.drawable.mastodon - is GitHubIdentity -> R.drawable.github - else -> R.drawable.github + is TwitterIdentity -> Res.drawable.x + is TelegramIdentity -> Res.drawable.telegram + is MastodonIdentity -> Res.drawable.mastodon + is GitHubIdentity -> Res.drawable.github + else -> Res.drawable.github } fun getIdentityClaimDescription(identity: IdentityClaimTag): StringResource = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DrawBanner.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DrawBanner.kt index 5cdec7ba24..6c0e953d31 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/DrawBanner.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/DrawBanner.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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/MessageButton.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/MessageButton.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/MessageButton.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/MessageButton.kt index 61f0404f45..0cf905215b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/MessageButton.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/MessageButton.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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileActions.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileActions.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileActions.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileActions.kt index 1dda2b37e8..a92d2ba576 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileActions.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileActions.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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileHeader.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileHeader.kt index c7c08b227f..3ab6379147 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileHeader.kt @@ -18,9 +18,8 @@ * 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.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header -import android.content.ClipData import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -47,11 +46,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.User @@ -62,16 +59,18 @@ import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.ui.actions.NewUserMetadataViewModel import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel +import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.commons.ui.note.platform.ZoomableImageDialog +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size100dp import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp import com.vitorpamplona.amethyst.commons.ui.theme.userProfileBorderModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.UserExternalIdentitiesViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel import kotlinx.coroutines.launch @Composable @@ -148,8 +147,7 @@ fun ZoomableUserPicture( onLongClick = { baseUser.profilePicture()?.let { it1 -> scope.launch { - val clipData = ClipData.newPlainText("profile picture url", it1) - clipboardManager.setClipEntry(ClipEntry(clipData)) + clipboardManager.setText(it1) } } }, @@ -191,7 +189,7 @@ private fun ProfilePictureUploadButton( accountViewModel: AccountViewModel, size: Dp, ) { - val postViewModel: NewUserMetadataViewModel = viewModel() + val postViewModel: NewUserMetadataViewModel = rememberViewModel { NewUserMetadataViewModel() } postViewModel.init(accountViewModel) var showGallerySelect by remember { mutableStateOf(false) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfilePaymentRailChips.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfilePaymentRailChips.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfilePaymentRailChips.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfilePaymentRailChips.kt index e8c363be58..8e0cfb0c06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfilePaymentRailChips.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfilePaymentRailChips.kt @@ -18,9 +18,8 @@ * 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.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header -import android.widget.Toast import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable @@ -44,7 +43,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -68,17 +66,18 @@ import com.vitorpamplona.amethyst.commons.resources.copy_to_clipboard import com.vitorpamplona.amethyst.commons.resources.send_payment_method_cashu import com.vitorpamplona.amethyst.commons.resources.send_payment_method_lightning import com.vitorpamplona.amethyst.commons.resources.send_payment_method_onchain +import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.Bolt12OffersDialog import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.abbreviateBolt12Offer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.payment.ProfilePaymentMethod +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.payment.rememberProfileClinkOffer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.commons.ui.theme.Size16Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.payment.ProfilePaymentMethod -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.payment.rememberProfileClinkOffer import com.vitorpamplona.quartz.experimental.clink.pointers.NOffer import com.vitorpamplona.quartz.nipA3PaymentTargets.PaymentTarget import com.vitorpamplona.quartz.nipA3PaymentTargets.PaymentTargetsEvent @@ -315,7 +314,7 @@ fun ProfilePaymentChip( copyValue: String? = null, icon: @Composable () -> Unit, ) { - val context = LocalContext.current + val shortNotice = rememberShortNotice() val clipboard = LocalClipboard.current val scope = rememberCoroutineScope() val copyLabel = stringRes(Res.string.copy_to_clipboard) @@ -328,7 +327,7 @@ fun ProfilePaymentChip( onLongClick = { scope.launch { clipboard.setText(copyValue) - Toast.makeText(context, copiedMessage, Toast.LENGTH_SHORT).show() + shortNotice.show(copiedMessage) } }, onLongClickLabel = copyLabel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileTopBar.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileTopBar.kt index cd8ba6b90d..8b698c1f9f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/ProfileTopBar.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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserNicknameCard.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/UserNicknameCard.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserNicknameCard.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/UserNicknameCard.kt index 283adadbc8..6e72d149b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserNicknameCard.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/UserNicknameCard.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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header 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/profile/header/UserProfileDropDownMenu.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt similarity index 90% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt index 4a3a9b18f4..e6fa083f0b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/UserProfileDropDownMenu.kt @@ -18,15 +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.screen.loggedIn.profile.header +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header -import android.content.Intent import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.nip85TrustedAssertions.ui.EditNicknameDialog @@ -47,6 +45,7 @@ import com.vitorpamplona.amethyst.commons.resources.unblock_user import com.vitorpamplona.amethyst.commons.ui.components.M3ActionDialog import com.vitorpamplona.amethyst.commons.ui.components.M3ActionRow import com.vitorpamplona.amethyst.commons.ui.components.M3ActionSection +import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList import com.vitorpamplona.amethyst.commons.ui.note.externalLinkForUser @@ -85,7 +84,7 @@ fun UserProfileDropDownMenu( ) { val clipboardManager = LocalClipboard.current val scope = rememberCoroutineScope() - val context = LocalContext.current + val textSharer = rememberTextSharer() // Share section M3ActionSection { @@ -102,18 +101,7 @@ fun UserProfileDropDownMenu( icon = MaterialSymbols.Share, text = stringRes(Res.string.quick_action_share), ) { - val sendIntent = - Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, externalLinkForUser(user)) - putExtra( - Intent.EXTRA_TITLE, - quickActionShareBrowserLinkStr, - ) - } - val shareIntent = Intent.createChooser(sendIntent, quickActionShareStr) - context.startActivity(shareIntent) + textSharer.share(externalLinkForUser(user), quickActionShareBrowserLinkStr, quickActionShareStr) onDismiss() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt index f3d9f04abf..7d6589a016 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/DisplayAppRecommendations.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.screen.loggedIn.profile.header.apps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.apps import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/UserAppRecommendationsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/UserAppRecommendationsFeedViewModel.kt similarity index 82% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/UserAppRecommendationsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/UserAppRecommendationsFeedViewModel.kt index ea92ae0860..307c49fa3d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/UserAppRecommendationsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/UserAppRecommendationsFeedViewModel.kt @@ -18,13 +18,15 @@ * 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.profile.header.apps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.apps import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserAppRecommendationsFeedViewModel( @@ -34,6 +36,9 @@ class UserAppRecommendationsFeedViewModel( val user: User, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserAppRecommendationsFeedViewModel(user) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserAppRecommendationsFeedViewModel(user) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/UserProfileAppRecommendationsFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/UserProfileAppRecommendationsFeedFilter.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/UserProfileAppRecommendationsFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/UserProfileAppRecommendationsFeedFilter.kt index 0d096b6ad3..01c705331d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/apps/UserProfileAppRecommendationsFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/apps/UserProfileAppRecommendationsFeedFilter.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.screen.loggedIn.profile.header.apps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.apps import com.vitorpamplona.amethyst.commons.feeds.AdditiveFeedFilter import com.vitorpamplona.amethyst.commons.feeds.sortedByDefaultFeedOrder diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt index 97eca91396..65d63a98b5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/badges/DisplayBadges.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/header/badges/DisplayBadges.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.screen.loggedIn.profile.header.badges +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.header.badges import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -90,6 +90,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOn diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/TabMutualConversations.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/TabMutualConversations.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/TabMutualConversations.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/TabMutualConversations.kt index 6f7ee2d539..585d43931a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/TabMutualConversations.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/TabMutualConversations.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.screen.loggedIn.profile.mutual +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.mutual import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -26,8 +26,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.mutual.dal.UserProfileMutualFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.mutual.dal.UserProfileMutualFeedViewModel @Composable fun TabMutualConversations( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedFilter.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedFilter.kt index dfbecdf667..31f58a6c75 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedFilter.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.screen.loggedIn.profile.mutual.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.mutual.dal import com.vitorpamplona.amethyst.commons.feeds.AdditiveFeedFilter import com.vitorpamplona.amethyst.commons.feeds.isRenderableRepost diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedViewModel.kt index cf1ca273b4..7c5eacef57 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/mutual/dal/UserProfileMutualFeedViewModel.kt @@ -18,14 +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.screen.loggedIn.profile.mutual.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.mutual.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserProfileMutualFeedViewModel( @@ -37,6 +39,9 @@ class UserProfileMutualFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileMutualFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileMutualFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt index ffa2d98cff..edc99bb1f1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/TabNotesNewThreads.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.screen.loggedIn.profile.newthreads +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.newthreads import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column @@ -45,11 +45,11 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.StickToTopOnPrepend import com.vitorpamplona.amethyst.commons.ui.feeds.WatchScrollToTop import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.newthreads.dal.UserProfileNewThreadsFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.newthreads.dal.UserProfileNewThreadsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.pinnedNotes.dal.UserProfilePinnedNotesFeedViewModel @Composable fun TabNotesNewThreads( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt index e0d6330ba0..8768b1b1ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.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.screen.loggedIn.profile.newthreads.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.newthreads.dal import com.vitorpamplona.amethyst.commons.feeds.AdditiveFeedFilter import com.vitorpamplona.amethyst.commons.feeds.isRenderableRepost diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadsFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadsFeedViewModel.kt index 4ddf48e4bb..c4ebe76d4d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadsFeedViewModel.kt @@ -18,14 +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.screen.loggedIn.profile.newthreads.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.newthreads.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserProfileNewThreadsFeedViewModel( @@ -37,6 +39,9 @@ class UserProfileNewThreadsFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileNewThreadsFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileNewThreadsFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/ProfileClinkOfferResolver.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/ProfileClinkOfferResolver.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/ProfileClinkOfferResolver.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/ProfileClinkOfferResolver.kt index 2709eb4916..f35a7fdbb5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/ProfileClinkOfferResolver.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/ProfileClinkOfferResolver.kt @@ -18,9 +18,8 @@ * 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.profile.payment +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.payment -import android.util.LruCache import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -32,7 +31,9 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.experimental.clink.pointers.ClinkPointerParser import com.vitorpamplona.quartz.experimental.clink.pointers.NOffer import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Id +import com.vitorpamplona.quartz.utils.cache.ConcurrentLruCache import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.withContext /** @@ -41,13 +42,13 @@ import kotlinx.coroutines.withContext * profile visit (and every relay-pushed kind-0 refresh while a profile is open) would * re-fetch the domain's nostr.json. Caches "no offer" results too so profiles without one * aren't re-hit. A [ResolvedClinkOffer] wrapper holds the nullable parsed pointer - * (LruCache can't store nulls); absence means "not fetched yet". + * (the cache can't store nulls); absence means "not fetched yet". */ private class ResolvedClinkOffer( val noffer: NOffer?, ) -private val clinkOfferNip05Cache = LruCache(256) +private val clinkOfferNip05Cache = ConcurrentLruCache(256) /** * Resolves a profile's advertised CLINK Offer, preferring the kind-0 `clink_offer` diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentContent.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/SendPaymentContent.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentContent.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/SendPaymentContent.kt index 8b98de44c5..b5021ebec4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentContent.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/SendPaymentContent.kt @@ -18,9 +18,8 @@ * 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.profile.payment +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.payment -import android.widget.Toast import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement @@ -59,7 +58,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType @@ -91,6 +89,7 @@ import com.vitorpamplona.amethyst.commons.resources.send_payment_method_onchain import com.vitorpamplona.amethyst.commons.resources.send_payment_no_methods import com.vitorpamplona.amethyst.commons.resources.send_payment_receipt_section import com.vitorpamplona.amethyst.commons.resources.send_payment_try_again +import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -100,6 +99,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.util.showAmount import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent +import com.vitorpamplona.quartz.utils.BigDecimal import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -469,7 +469,7 @@ private fun MethodChip( enabled: Boolean, onSelect: () -> Unit, ) { - val context = LocalContext.current + val shortNotice = rememberShortNotice() val clipboard = LocalClipboard.current val scope = rememberCoroutineScope() val copyLabel = stringRes(Res.string.copy_to_clipboard) @@ -493,7 +493,7 @@ private fun MethodChip( { scope.launch { clipboard.setText(value) - Toast.makeText(context, copiedMessage, Toast.LENGTH_SHORT).show() + shortNotice.show(copiedMessage) } } }, @@ -596,7 +596,7 @@ private fun AmountSection( SuggestionChip( enabled = enabled, onClick = { onAmountChange(amount.toString()) }, - label = { Text("⚡ ${showAmount(amount.toBigDecimal())}") }, + label = { Text("⚡ ${showAmount(BigDecimal(amount))}") }, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt index c695660415..74ed293e4d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/payment/SendPaymentScreen.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.screen.loggedIn.profile.payment +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.payment import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -40,7 +40,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -96,6 +95,7 @@ import com.vitorpamplona.amethyst.commons.resources.zap_type_private import com.vitorpamplona.amethyst.commons.resources.zap_type_private_explainer import com.vitorpamplona.amethyst.commons.resources.zap_type_public import com.vitorpamplona.amethyst.commons.resources.zap_type_public_explainer +import com.vitorpamplona.amethyst.commons.service.ClinkOfferPayer import com.vitorpamplona.amethyst.commons.service.nwc.nwcFailureDetail import com.vitorpamplona.amethyst.commons.service.nwc.nwcTimeoutMessage import com.vitorpamplona.amethyst.commons.ui.loadStringRes @@ -108,10 +108,11 @@ import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.FeeTier import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.rateFor import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.ui.wallet.payInvoice +import com.vitorpamplona.amethyst.commons.ui.wallet.rememberWalletAppLauncher +import com.vitorpamplona.amethyst.commons.util.DecimalPatternFormatter import com.vitorpamplona.amethyst.commons.util.showAmount import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.ClinkOfferPayer -import com.vitorpamplona.amethyst.ui.note.payViaIntent import com.vitorpamplona.quartz.experimental.clink.common.SatRange import com.vitorpamplona.quartz.experimental.clink.offers.OfferErrorCode import com.vitorpamplona.quartz.experimental.clink.pointers.ClinkPointerParser @@ -121,10 +122,12 @@ import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent import com.vitorpamplona.quartz.nipBCOnchainZaps.chain.FeeEstimates import com.vitorpamplona.quartz.nipBCOnchainZaps.taproot.SegwitAddress import com.vitorpamplona.quartz.nipBCOnchainZaps.taproot.TaprootAddress +import com.vitorpamplona.quartz.utils.BigDecimal import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -180,7 +183,7 @@ private fun SendPaymentLoaded( nav: INav, modifier: Modifier = Modifier, ) { - val context = LocalContext.current + val walletLauncher = rememberWalletAppLauncher() val scope = rememberCoroutineScope() val userInfo by observeUserInfo(user, accountViewModel) @@ -368,7 +371,7 @@ private fun SendPaymentLoaded( selectedMethod == ProfilePaymentMethod.CASHU -> stringRes( Res.string.send_payment_cashu_balance, - showAmount((cashuFunding?.bestSingleMintSats ?: 0L).toBigDecimal()), + showAmount(BigDecimal(cashuFunding?.bestSingleMintSats ?: 0L)), ) selectedMethod == ProfilePaymentMethod.CLINK && clinkRange?.min != null && clinkRange?.max != null -> stringRes(Res.string.clink_offer_amount_range, clinkRange?.min.toString(), clinkRange?.max.toString()) @@ -457,9 +460,8 @@ private fun SendPaymentLoaded( // startActivity belongs on Main. sendSats now delivers onNewInvoice // there already; the hop also covers any other caller of payBolt11. scope.launch { - payViaIntent( + walletLauncher.payInvoice( invoice, - context, noWalletFoundStr, onPaid = { scope.launch { postStage(PaymentFlowStage.Success(successTitle, sentToWalletLabel)) } }, onError = { scope.launch { postStage(PaymentFlowStage.Failure(it)) } }, @@ -664,7 +666,7 @@ private fun SendPaymentLoaded( canSend = canSend, sendLabel = if (amountSats != null && amountSats > 0) { - stringRes(Res.string.send_payment_pay_button, showAmount(amountSats.toBigDecimal())) + stringRes(Res.string.send_payment_pay_button, showAmount(BigDecimal(amountSats))) } else { stringRes(Res.string.send_payment_pay_button_empty) }, @@ -800,7 +802,7 @@ private fun OnchainFeeSection( stringRes( Res.string.onchain_send_fee_tier_label_rate_eta, stringRes(tier.labelRes), - "%.1f".format(rate), + DecimalPatternFormatter("0.0").format(rate), stringRes(tier.etaLabelRes), ) } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedFilter.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedFilter.kt index 3e5e8ee5ff..269e75690e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedFilter.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.screen.loggedIn.profile.pinnedNotes.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.pinnedNotes.dal import com.vitorpamplona.amethyst.commons.feeds.FeedFilter import com.vitorpamplona.amethyst.commons.model.Account diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedViewModel.kt index 939131cfab..572a8bc80d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/pinnedNotes/dal/UserProfilePinnedNotesFeedViewModel.kt @@ -18,14 +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.screen.loggedIn.profile.pinnedNotes.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.pinnedNotes.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class UserProfilePinnedNotesFeedViewModel( @@ -37,6 +39,9 @@ class UserProfilePinnedNotesFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfilePinnedNotesFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfilePinnedNotesFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelayFeedView.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/relays/RelayFeedView.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelayFeedView.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/relays/RelayFeedView.kt index 815f999872..652364aee7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/RelayFeedView.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/relays/RelayFeedView.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.screen.loggedIn.profile.relays +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.relays import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/TabRelays.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/relays/TabRelays.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/TabRelays.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/relays/TabRelays.kt index e88dedd638..ac9d96be78 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/relays/TabRelays.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/relays/TabRelays.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.screen.loggedIn.profile.relays +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.relays import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -28,8 +28,8 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.User +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.RelayFeedViewModel @@ -40,7 +40,7 @@ fun TabRelays( accountViewModel: AccountViewModel, nav: INav, ) { - val feedViewModel: RelayFeedViewModel = viewModel() + val feedViewModel: RelayFeedViewModel = rememberViewModel { RelayFeedViewModel() } val lifeCycleOwner = LocalLifecycleOwner.current diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt index db54f9d6f8..6f4ad030b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/ReportsTabHeader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/ReportsTabHeader.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.screen.loggedIn.profile.reports +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -28,9 +28,9 @@ import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.number_reports import com.vitorpamplona.amethyst.commons.resources.reports +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel @Composable fun ReportsTabHeader( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/TabReports.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/TabReports.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/TabReports.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/TabReports.kt index f6aea85cf3..574e2f5545 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/TabReports.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/TabReports.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.screen.loggedIn.profile.reports +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -36,10 +36,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.reports.dal.UserProfileReportFeedViewModel @Composable fun TabReports( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/dal/UserProfileReportFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/dal/UserProfileReportFeedViewModel.kt similarity index 88% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/dal/UserProfileReportFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/dal/UserProfileReportFeedViewModel.kt index 70f1dd0a2b..c200562104 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/reports/dal/UserProfileReportFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/reports/dal/UserProfileReportFeedViewModel.kt @@ -18,22 +18,25 @@ * 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.profile.reports.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.reports.dal import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.User import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.stateIn +import kotlin.reflect.KClass @Stable class UserProfileReportFeedViewModel( @@ -76,6 +79,9 @@ class UserProfileReportFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileReportFeedViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileReportFeedViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt index fd714997ed..70015ce439 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/TabReceivedZaps.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.screen.loggedIn.profile.zaps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight @@ -32,10 +32,10 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsViewModel import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsViewModel @Composable fun TabReceivedZaps( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapNoteCompose.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/ZapNoteCompose.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapNoteCompose.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/ZapNoteCompose.kt index 9eeb4f3cc1..f8f6a696ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapNoteCompose.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/ZapNoteCompose.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.screen.loggedIn.profile.zaps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -42,13 +42,13 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.commons.ui.note.UserActionOptions import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps.dal.ZapAmount import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.commons.ui.theme.Size55dp import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.util.showAmountInteger import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.dal.ZapAmount import com.vitorpamplona.quartz.utils.BigDecimal @Composable @@ -77,7 +77,7 @@ fun RenderZapNoteSlimPreview() { ThemeComparisonColumn { RenderZapNoteSlim( - ZapAmount(user1, BigDecimal.TEN), + ZapAmount(user1, BigDecimal(10)), accountViewModel, EmptyNav(), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt similarity index 91% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt index 113750a506..762c7fb086 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/ZapTabHeader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/ZapTabHeader.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.screen.loggedIn.profile.zaps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -26,10 +26,10 @@ import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.zaps +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.util.showAmountInteger import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.zaps.dal.UserProfileZapsViewModel @Composable fun ZapTabHeader( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/dal/UserProfileZapsViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/dal/UserProfileZapsViewModel.kt similarity index 84% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/dal/UserProfileZapsViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/dal/UserProfileZapsViewModel.kt index 31f5b82918..e89f8d87f0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/zaps/dal/UserProfileZapsViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/profile/zaps/dal/UserProfileZapsViewModel.kt @@ -18,13 +18,14 @@ * 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.profile.zaps.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.profile.zaps.dal import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.cache.LocalCache @@ -34,13 +35,16 @@ import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent import com.vitorpamplona.quartz.nipB1Bolt12Zaps.zap.Bolt12ZapEvent import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent import com.vitorpamplona.quartz.utils.BigDecimal +import com.vitorpamplona.quartz.utils.plus import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.stateIn +import kotlin.reflect.KClass @Immutable data class ZapAmount( @@ -59,14 +63,15 @@ class UserProfileZapsViewModel( tags = mapOf("p" to listOf(user.pubkeyHex)), ) + // Largest amount first. The shared BigDecimal is not Comparable, so the order comes from the sign of the difference. val sortingModel: Comparator = - compareByDescending { it.amount }.thenBy { it.user.pubkeyHex } + Comparator { a, b -> b.amount.subtract(a.amount).signum() }.thenBy { it.user.pubkeyHex } suspend fun mapRequest(zapEvent: ZapReceiptEvent): ZapAmount? { val zapRequest = zapEvent.zapRequest ?: return ZapAmount( LocalCache.getOrCreateUser(zapEvent.pubKey), - zapEvent.amount ?: BigDecimal.ZERO, + zapEvent.amount ?: BigDecimal(0), ) return if (zapRequest.isPrivateZap()) { @@ -76,24 +81,24 @@ class UserProfileZapsViewModel( if (cachedPrivateRequest != null) { ZapAmount( LocalCache.getOrCreateUser(cachedPrivateRequest.pubKey), - zapEvent.amount ?: BigDecimal.ZERO, + zapEvent.amount ?: BigDecimal(0), ) } else { ZapAmount( LocalCache.getOrCreateUser(zapRequest.pubKey), - zapEvent.amount ?: BigDecimal.ZERO, + zapEvent.amount ?: BigDecimal(0), ) } } else { ZapAmount( LocalCache.getOrCreateUser(zapRequest.pubKey), - zapEvent.amount ?: BigDecimal.ZERO, + zapEvent.amount ?: BigDecimal(0), ) } } else { ZapAmount( LocalCache.getOrCreateUser(zapRequest.pubKey), - zapEvent.amount ?: BigDecimal.ZERO, + zapEvent.amount ?: BigDecimal(0), ) } } @@ -128,7 +133,7 @@ class UserProfileZapsViewModel( else -> null } if (zapAmount != null) { - val existingAmount = results[zapAmount.user] ?: BigDecimal.ZERO + val existingAmount = results[zapAmount.user] ?: BigDecimal(0) results[zapAmount.user] = existingAmount + zapAmount.amount } } @@ -152,11 +157,11 @@ class UserProfileZapsViewModel( val totalReceivedZaps = receivedZapAmountsByUser - .map { it.sumOf { it.amount } } + .map { amounts -> amounts.fold(BigDecimal(0)) { total, zap -> total + zap.amount } } .flowOn(Dispatchers.IO) .stateIn( viewModelScope, - initialValue = BigDecimal.ZERO, + initialValue = BigDecimal(0), started = SharingStarted.Lazily, ) @@ -165,6 +170,9 @@ class UserProfileZapsViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = UserProfileZapsViewModel(user, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = UserProfileZapsViewModel(user, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AppSettingsScreen.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AppSettingsScreen.kt index 5fbd6cc27d..6d45f4c472 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AppSettingsScreen.kt @@ -18,9 +18,8 @@ * 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.settings +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings -import android.content.Context import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -53,7 +52,6 @@ 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.LocalContext import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.font.FontFamily @@ -62,8 +60,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.core.os.LocaleListCompat -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -120,6 +116,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppPlatform import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SegmentedChoiceTile import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsBlockTile import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsControlRow @@ -134,12 +131,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.toFontFamily import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableList -import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.flow.MutableStateFlow import org.jetbrains.compose.resources.StringResource -import org.xmlpull.v1.XmlPullParser -import org.xmlpull.v1.XmlPullParserException -import java.io.IOException @Composable fun SettingsScreen( @@ -436,9 +429,7 @@ private fun AccentColorTile(sharedPrefs: UiSettingsFlow) { // matches the app's other "opens a picker" rows instead of leaving a lone text-field dropdown here. @Composable private fun LanguageTile(sharedPrefs: UiSettingsFlow) { - val context = LocalContext.current - - val languageEntries = remember { context.getLangPreferenceDropdownEntries() } + val languageEntries = LocalAppPlatform.current.rememberAppLanguages() val languageList = remember { languageEntries.keys.map { TitleExplainer(it) }.toImmutableList() } val language by sharedPrefs.preferredLanguage.collectAsState() @@ -482,40 +473,6 @@ private fun LanguageTile(sharedPrefs: UiSettingsFlow) { } } -fun Context.getLocaleListFromXml(): LocaleListCompat { - val tagsList = mutableListOf() - try { - val xpp: XmlPullParser = resources.getXml(R.xml.locales_config) - while (xpp.eventType != XmlPullParser.END_DOCUMENT) { - if (xpp.eventType == XmlPullParser.START_TAG && xpp.name == "locale") { - tagsList.add(xpp.getAttributeValue(0)) - } - xpp.next() - } - } catch (e: XmlPullParserException) { - e.printStackTrace() - } catch (e: IOException) { - e.printStackTrace() - } - - return LocaleListCompat.forLanguageTags(tagsList.joinToString(",")) -} - -fun Context.getLangPreferenceDropdownEntries(): ImmutableMap { - val localeList = getLocaleListFromXml() - val map = mutableMapOf() - - for (a in 0 until localeList.size()) { - localeList[a].let { - map.put( - it!!.getDisplayName(it).replaceFirstChar { char -> char.uppercase() }, - it.toLanguageTag(), - ) - } - } - return map.toImmutableMap() -} - fun getLanguageIndex( languageEntries: ImmutableMap, language: String?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NamecoinSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NamecoinSettingsScreen.kt similarity index 82% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NamecoinSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NamecoinSettingsScreen.kt index c20834d797..2a22df38c3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NamecoinSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NamecoinSettingsScreen.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.screen.loggedIn.settings +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -33,34 +33,28 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.preferences.NamecoinSettingsStore import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.namecoin_settings +import com.vitorpamplona.amethyst.commons.service.namecoin.NamecoinClients import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices import com.vitorpamplona.amethyst.commons.ui.stringRes -import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumXClient -import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinCoreRpcClient import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun NamecoinSettingsScreen(nav: INav) { - NamecoinSettingsScreen( - Amethyst.instance.namecoinPrefs, - electrumXClient = { Amethyst.instance.electrumXClient }, - namecoinCoreRpcClient = { Amethyst.instance.namecoinCoreRpcClient }, - nav, - ) + val services = LocalAppServices.current + NamecoinSettingsScreen(services.namecoinSettings, services.namecoinClients, nav) } @OptIn(ExperimentalMaterial3Api::class) @Composable fun NamecoinSettingsScreen( namecoinPrefs: NamecoinSettingsStore, - electrumXClient: () -> ElectrumXClient, - namecoinCoreRpcClient: () -> NamecoinCoreRpcClient, + clients: NamecoinClients, nav: INav, ) { val namecoinSettings by namecoinPrefs.settings.collectAsStateWithLifecycle() @@ -92,14 +86,13 @@ fun NamecoinSettingsScreen( onReset = { scope.launch { namecoinPrefs.reset() } }, - onTestServer = { server -> electrumXClient().testServer(server) }, + onTestServer = { server -> clients.testElectrumxServer(server) }, onPinCert = { pem -> scope.launch { namecoinPrefs.addPinnedCert(pem) // Share the pinned PEM across both backends so the // user only has to confirm a self-signed cert once. - electrumXClient().addPinnedCert(pem) - namecoinCoreRpcClient().addPinnedCert(pem) + clients.addPinnedCert(pem) } }, onSetBackend = { backend -> @@ -108,7 +101,7 @@ fun NamecoinSettingsScreen( onSetCoreRpcConfig = { cfg -> scope.launch { namecoinPrefs.setCoreRpcConfig(cfg) - namecoinCoreRpcClient().setConfig(cfg) + clients.setCoreRpcConfig(cfg) } }, onSetFallbackToCustomElectrumx = { enabled -> @@ -117,7 +110,7 @@ fun NamecoinSettingsScreen( onSetFallbackToDefaultElectrumx = { enabled -> scope.launch { namecoinPrefs.setFallbackToDefaultElectrumx(enabled) } }, - onTestCoreRpc = { cfg -> namecoinCoreRpcClient().probe(cfg) }, + onTestCoreRpc = { cfg -> clients.probeCoreRpc(cfg) }, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NamecoinSettingsSection.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NamecoinSettingsSection.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NamecoinSettingsSection.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NamecoinSettingsSection.kt index 629237365d..06ba21c237 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NamecoinSettingsSection.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NamecoinSettingsSection.kt @@ -18,9 +18,8 @@ * 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.settings +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings -import android.os.Build import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically import androidx.compose.animation.shrinkVertically @@ -60,7 +59,6 @@ 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 import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -68,6 +66,7 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.vitorpamplona.amethyst.commons.browser.OmniboxInput import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.namecoin.NamecoinSettings @@ -89,14 +88,16 @@ import com.vitorpamplona.amethyst.commons.resources.namecoin_tls_info import com.vitorpamplona.amethyst.commons.ui.note.DateTimeStyle import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.util.deviceDescription import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.DEFAULT_ELECTRUMX_SERVERS import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinBackend import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinCoreRpcConfig import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.RpcProbeResult import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ServerTestResult +import com.vitorpamplona.quartz.utils.currentTimeMillis import kotlinx.coroutines.launch -import java.util.Locale +import kotlin.math.roundToLong /** * Complete settings section for Namecoin ElectrumX server configuration. @@ -349,7 +350,7 @@ private fun TestConnectionSection( ) } } - lastTestTimestamp = System.currentTimeMillis() + lastTestTimestamp = currentTimeMillis() isTesting = false // Show confirmation dialog for each new cert if (newCerts.isNotEmpty()) { @@ -512,9 +513,8 @@ private fun DiagnosticCard( // Last test timestamp if (lastTestTimestamp != null) { - val context = LocalContext.current val formatted = - remember(lastTestTimestamp, context) { + remember(lastTestTimestamp) { formatDateTime(lastTestTimestamp, DateTimeStyle.MEDIUM, DateTimeStyle.SHORT) } val successCount = testResults.count { it.success } @@ -535,7 +535,7 @@ private fun DiagnosticCard( // Device info DiagnosticRow( label = stringRes(Res.string.namecoin_device_info), - value = "${Build.MANUFACTURER} ${Build.MODEL}, Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})", + value = deviceDescription(), ) Spacer(Modifier.height(4.dp)) @@ -1108,13 +1108,7 @@ private fun NamecoinCoreRpcSection( !candidate.usePinnedTrustStore ) ) { - val host = - try { - java.net.URI(candidate.url).host - ?: candidate.url - } catch (_: Exception) { - candidate.url - } + val host = OmniboxInput.hostOf(candidate.url) ?: candidate.url pendingPin = PendingCertPin( serverHost = host, @@ -1167,7 +1161,7 @@ private fun NamecoinCoreRpcSection( val chain = probe.chain ?: "?" val blocks = probe.blocks?.toString() ?: "?" val pct = - probe.verificationProgress?.let { String.format(Locale.ROOT, "%.2f%%", it * 100) } + probe.verificationProgress?.let { percentTwoDecimals(it) } ?: "?" Text( "chain=$chain height=$blocks sync=$pct" + @@ -1316,3 +1310,9 @@ private fun ToggleRow( ) } } + +/** [fraction] as a percentage with exactly two decimals and a `.` mark ("99.50%"), as Core RPC reports it. */ +private fun percentTwoDecimals(fraction: Double): String { + val hundredths = (fraction * 10_000).roundToLong() + return "${hundredths / 100}.${(hundredths % 100).toString().padStart(2, '0')}%" +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/OtsSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/OtsSettingsScreen.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/OtsSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/OtsSettingsScreen.kt index 3b452871b4..e498deeffb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/OtsSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/OtsSettingsScreen.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.screen.loggedIn.settings +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -33,7 +33,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.preferences.OtsSettingsStore import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.ots_explorer_settings @@ -41,13 +40,14 @@ import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow import com.vitorpamplona.amethyst.commons.tor.TorType import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices import com.vitorpamplona.amethyst.commons.ui.stringRes import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun OtsSettingsScreen(nav: INav) { - OtsSettingsScreen(Amethyst.instance.otsPrefs, Amethyst.instance.torPrefs.value, nav) + LocalAppServices.current.let { OtsSettingsScreen(it.otsSettings, it.torSettings, nav) } } @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/OtsSettingsSection.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/OtsSettingsSection.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/OtsSettingsSection.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/OtsSettingsSection.kt index bc531b28e0..7ce42af7a0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/OtsSettingsSection.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/OtsSettingsSection.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.screen.loggedIn.settings +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -64,7 +64,7 @@ import com.vitorpamplona.amethyst.commons.resources.ots_reset_to_auto_select import com.vitorpamplona.amethyst.commons.resources.save import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange -import com.vitorpamplona.quartz.nip03Timestamp.okhttp.OkHttpBitcoinExplorer +import com.vitorpamplona.quartz.nip03Timestamp.BitcoinExplorerUrls /** * Settings section for configuring the blockchain explorer used for OTS verification. @@ -180,7 +180,7 @@ private fun ActiveExplorerDisplay( val isCustom = settings.hasCustomExplorer val activeUrl = settings.normalizedUrl() - ?: if (isTorActive) OkHttpBitcoinExplorer.MEMPOOL_API_URL else OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL + ?: if (isTorActive) BitcoinExplorerUrls.MEMPOOL else BitcoinExplorerUrls.BLOCKSTREAM Column { Row( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46ConnectedAppsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/nip46/Nip46ConnectedAppsScreen.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46ConnectedAppsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/nip46/Nip46ConnectedAppsScreen.kt index 3b21b83d8e..d39e0e918c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46ConnectedAppsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/nip46/Nip46ConnectedAppsScreen.kt @@ -18,9 +18,8 @@ * 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.settings.nip46 +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46 -import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -56,14 +55,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46ClientInfo import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46PermissionAuthorizer import com.vitorpamplona.amethyst.commons.connectedApps.signers.AppSignerPolicy @@ -84,11 +81,14 @@ import com.vitorpamplona.amethyst.commons.resources.nip46_signer_apps_empty import com.vitorpamplona.amethyst.commons.resources.nip46_signer_manage_apps import com.vitorpamplona.amethyst.commons.resources.nip46_signer_reconnecting import com.vitorpamplona.amethyst.commons.resources.nip46_signer_remote_app +import com.vitorpamplona.amethyst.commons.service.AppServices +import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.note.timeAgo +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.stringRes -import com.vitorpamplona.amethyst.commons.util.toTimeAgo import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -125,7 +125,8 @@ fun Nip46ConnectedAppsScreen( val reconnectingStr = stringRes(Res.string.nip46_signer_reconnecting) val account = accountViewModel.account val signerPubKey = remember { account.signer.pubKey } - val context = LocalContext.current + val shortNotice = rememberShortNotice() + val services = LocalAppServices.current // Live relay state so each app shows whether the signer currently reaches it. An app that brought // its own relays (nostrconnect) is judged on those; a bunker-flow app rides the inbox relays. @@ -137,7 +138,7 @@ fun Nip46ConnectedAppsScreen( var refreshKey by remember { mutableIntStateOf(0) } LaunchedEffect(refreshKey) { - items = withContext(Dispatchers.Default) { loadNip46Apps(signerPubKey) } + items = withContext(Dispatchers.Default) { loadNip46Apps(services, signerPubKey) } } Scaffold( @@ -187,7 +188,7 @@ fun Nip46ConnectedAppsScreen( online = online, onReconnect = { account.client.reconnect(ignoreRetryDelays = true) - Toast.makeText(context, reconnectingStr, Toast.LENGTH_SHORT).show() + shortNotice.show(reconnectingStr) }, onClick = { nav.nav(Route.ConnectedAppDetail(entry.coordinate)) }, ) @@ -241,7 +242,7 @@ private fun Nip46AppCard( val meta = buildList { if (relayCount > 0) add(pluralStringRes(Res.plurals.nip46_signer_app_relay_count, relayCount, relayCount)) - entry.lastUsedSeconds?.let { add(stringRes(Res.string.nip46_signer_app_last_used, it.toTimeAgo().trim())) } + entry.lastUsedSeconds?.let { add(stringRes(Res.string.nip46_signer_app_last_used, timeAgo(it).trim())) } }.joinToString(" · ") if (meta.isNotEmpty()) { Text( @@ -291,7 +292,7 @@ private val LiveGreen = Color(0xFF3DDC84) /** A colored dot: green when the signer currently reaches the relay(s), muted otherwise. */ @Composable -internal fun Nip46StatusDot(online: Boolean) { +fun Nip46StatusDot(online: Boolean) { Box(Modifier.size(8.dp).clip(CircleShape).background(if (online) LiveGreen else MaterialTheme.colorScheme.outline)) } @@ -301,7 +302,7 @@ internal fun Nip46StatusDot(online: Boolean) { * pill inside a clickable card doesn't also open the card. */ @Composable -internal fun Nip46ReconnectPill(onClick: () -> Unit) { +fun Nip46ReconnectPill(onClick: () -> Unit) { Text( stringRes(Res.string.nip46_signer_app_reconnect), style = MaterialTheme.typography.labelSmall, @@ -317,7 +318,7 @@ internal fun Nip46ReconnectPill(onClick: () -> Unit) { /** A [Nip46StatusDot] + Connected/Offline label showing whether the signer currently reaches an app's relays. */ @Composable -internal fun Nip46LiveStatus(online: Boolean) { +fun Nip46LiveStatus(online: Boolean) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) { Nip46StatusDot(online) Text( @@ -334,7 +335,7 @@ internal fun Nip46LiveStatus(online: Boolean) { * `null` when neither resolves to any relay (nothing to show a status for). "Online" means at least * one of the app's relays is in [connectedRelays]. */ -internal fun nip46AppOnline( +fun nip46AppOnline( appRelays: Set, inboxRelays: Set, connectedRelays: Set, @@ -352,7 +353,7 @@ internal fun nip46AppOnline( * user's IP (see BrowserIconRegistry). */ @Composable -internal fun Nip46AppIcon( +fun Nip46AppIcon( image: String?, modifier: Modifier = Modifier, ) { @@ -388,7 +389,7 @@ internal fun Nip46AppIcon( * advertised one, otherwise its npub. Shared by the list card and the detail header so a row and * the screen it opens always agree. */ -internal fun nip46ClientSubtitle( +fun nip46ClientSubtitle( url: String?, clientPubKey: HexKey, ): String { @@ -402,9 +403,12 @@ internal fun nip46ClientSubtitle( return host ?: runCatching { NPub.create(clientPubKey) }.getOrDefault(clientPubKey.take(12) + "…") } -private suspend fun loadNip46Apps(signerPubKey: HexKey): List { - val store = Amethyst.instance.signerPermissionStore - val clientStore = Amethyst.instance.nip46ClientStore +private suspend fun loadNip46Apps( + services: AppServices, + signerPubKey: HexKey, +): List { + val store = services.signerPermissionStore + val clientStore = services.nip46ClientStore // allPolicies() already carries each app's policy — read it from the map instead of a second // loadPolicy() call per app. return store diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/BitcoinExplorerUrls.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/BitcoinExplorerUrls.kt new file mode 100644 index 0000000000..9e95c31dfb --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/BitcoinExplorerUrls.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 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.quartz.nip03Timestamp + +/** The public Esplora-compatible block explorers OTS verification can ask. */ +object BitcoinExplorerUrls { + /** Blockstream's API; doesn't accept Tor. */ + const val BLOCKSTREAM = "https://blockstream.info/api" + + /** mempool.space's API; accepts Tor. */ + const val MEMPOOL = "https://mempool.space/api/" +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinBackend.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinBackend.kt index 74fbf70ce8..1266943e7a 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinBackend.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinBackend.kt @@ -129,3 +129,32 @@ data class NamecoinFallbackPolicy( val NONE = NamecoinFallbackPolicy() } } + +data class RpcProbeResult( + val success: Boolean, + val elapsedMs: Long, + val chain: String? = null, + val blocks: Int? = null, + val verificationProgress: Double? = null, + val initialBlockDownload: Boolean? = null, + val error: String? = null, + /** + * PEM-encoded server leaf certificate captured during the probe. + * Only populated when the URL scheme is https and the TLS handshake + * succeeded (even if the subsequent HTTP request failed). Used by + * the Settings UI to prompt for a TOFU pin. + */ + val serverCertPem: String? = null, + /** + * SHA-256 fingerprint of the captured leaf certificate, formatted + * as colon-separated uppercase hex bytes (matches + * [ServerTestResult.certFingerprint] for ElectrumX). + */ + val certFingerprint: String? = null, + /** + * True when the probe failed specifically because the TLS handshake + * was rejected (self-signed cert, untrusted CA, hostname mismatch). + * Used by the Settings UI to suggest a TOFU pin as the fix. + */ + val tlsHandshakeFailed: Boolean = false, +) diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip03Timestamp/okhttp/OkHttpBitcoinExplorer.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip03Timestamp/okhttp/OkHttpBitcoinExplorer.kt index 64485cdb1e..dd3dc314b1 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip03Timestamp/okhttp/OkHttpBitcoinExplorer.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip03Timestamp/okhttp/OkHttpBitcoinExplorer.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.quartz.nip03Timestamp.okhttp import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper +import com.vitorpamplona.quartz.nip03Timestamp.BitcoinExplorerUrls import com.vitorpamplona.quartz.nip03Timestamp.ots.BitcoinExplorer import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache @@ -116,9 +117,9 @@ class OkHttpBitcoinExplorer( companion object { // doesn't accept Tor - const val BLOCKSTREAM_API_URL = "https://blockstream.info/api" + const val BLOCKSTREAM_API_URL = BitcoinExplorerUrls.BLOCKSTREAM // accepts Tor - const val MEMPOOL_API_URL = "https://mempool.space/api/" + const val MEMPOOL_API_URL = BitcoinExplorerUrls.MEMPOOL } } diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinCoreRpcClient.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinCoreRpcClient.kt index 897ab9b27d..f77bf6519a 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinCoreRpcClient.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/NamecoinCoreRpcClient.kt @@ -567,31 +567,3 @@ class NamecoinCoreRpcClient( } /** Outcome of a `Test RPC` probe in Settings. */ -data class RpcProbeResult( - val success: Boolean, - val elapsedMs: Long, - val chain: String? = null, - val blocks: Int? = null, - val verificationProgress: Double? = null, - val initialBlockDownload: Boolean? = null, - val error: String? = null, - /** - * PEM-encoded server leaf certificate captured during the probe. - * Only populated when the URL scheme is https and the TLS handshake - * succeeded (even if the subsequent HTTP request failed). Used by - * the Settings UI to prompt for a TOFU pin. - */ - val serverCertPem: String? = null, - /** - * SHA-256 fingerprint of the captured leaf certificate, formatted - * as colon-separated uppercase hex bytes (matches - * [ServerTestResult.certFingerprint] for ElectrumX). - */ - val certFingerprint: String? = null, - /** - * True when the probe failed specifically because the TLS handshake - * was rejected (self-signed cert, untrusted CA, hostname mismatch). - * Used by the Settings UI to suggest a TOFU pin as the fix. - */ - val tlsHandshakeFailed: Boolean = false, -)