From 642b59c9d12b3b15db3eb88e948a8ae52c2e8ce5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 02:33:33 +0000 Subject: [PATCH 01/20] refactor: cut shared-screen blockers (step 6 wave 3 seams) - String helpers: files that only read Compose resources import the commons stringRes, not the app's Android-resource overloads. browser_address_hint and napplet_untitled get Compose copies (every locale) so the browser and connected-apps screens stop reading commons' Android R. - ReactionsRow/LikeReaction/ZapReaction and LoadCityName are imported through the note-platform shims instead of the app implementations. - QR: the drawer moves to commonsUI with the encoding behind encodeQrMatrix (zxing core on JVM/Android, nothing yet on iOS); KeepScreenBrightAndAwake becomes expect/actual; ScanOutcome is shared and the camera scanner is an AppPlatform slot. - AppPlatform gains the app launcher and the Concord/napplet/manifest/favicon image models. favoriteCoordinateOf moves to commons. - New AppServices port (favorites, browser history and icons, napplet and signer permissions, Tor settings) installed as LocalAppServices; AccountViewModelHost gains the Tor relay evaluation and NIP-42 auth state. Screens read these instead of Amethyst.instance. - uriToRoute and its helpers leave MainActivity for UriToRoute.kt and decode with the KMP UrlEncoder; findQueryParameterValue leaves AppNavigation. - Settings rows split out of AppSettingsScreen; formatGrouped replaces NumberFormat; Uuid replaces java.util.UUID; javaClass and toBigDecimalOrNull swapped for KMP forms. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/favorites/FavoriteAppLauncher.kt | 16 +- .../vitorpamplona/amethyst/ui/MainActivity.kt | 368 ----------------- .../vitorpamplona/amethyst/ui/UriToRoute.kt | 388 ++++++++++++++++++ .../amethyst/ui/navigation/AppNavigation.kt | 31 -- .../amethyst/ui/navigation/QueryParameters.kt | 52 +++ .../ui/navigation/bottombars/AppBottomBar.kt | 11 +- .../bottombars/GroupBottomBarEntries.kt | 2 +- .../topbars/AroundMeLocationLabel.kt | 2 +- .../nip22Comments/GenericCommentPostScreen.kt | 2 +- .../ui/note/share/ShareNoteAsQrScreen.kt | 4 +- .../amethyst/ui/note/types/StaticWebsite.kt | 10 +- .../ui/platform/AndroidAppPlatform.kt | 59 ++- .../ui/platform/AndroidAppServices.kt | 49 +++ .../amethyst/ui/screen/UserFeedViewModel.kt | 8 +- .../loggedIn/AndroidAccountViewModelHost.kt | 10 + .../backups/BackupConflictEventViews.kt | 4 +- .../backups/BackupConflictListViews.kt | 2 +- .../backups/BackupConflictPresentation.kt | 2 +- .../screen/loggedIn/browser/BrowserScreen.kt | 49 +-- .../screen/loggedIn/buzz/BuzzInviteScreen.kt | 10 +- .../ui/screen/loggedIn/buzz/RelayStatusBar.kt | 3 +- .../detail/CalendarEventDetailScreen.kt | 2 +- .../chats/cordnGroup/CordnGroupInfoScreen.kt | 2 +- .../chats/geohashChat/GeohashChatScreen.kt | 2 +- .../chats/geohashChat/GeohashChatsScreen.kt | 2 +- .../chats/geohashChat/NewGeohashChatScreen.kt | 2 +- .../concord/ConcordChannelListScreen.kt | 2 +- .../concord/ConcordHomeScreen.kt | 1 + .../concord/ConcordMetadataForm.kt | 1 + .../header/LiveStreamGoalHeader.kt | 2 +- .../relayGroup/RelayGroupMetadataScreen.kt | 2 +- .../chats/rooms/ChatroomHeaderCompose.kt | 2 +- .../RenderPublicChatChannelThumb.kt | 4 +- .../nip72Communities/CommunityCard.kt | 4 +- .../loggedIn/discover/nip90DVMs/DVMCard.kt | 4 +- .../nip99Classifieds/ClassifiedsThumb.kt | 3 +- .../loggedIn/favorites/FavoriteAppsScreen.kt | 21 +- .../followPacks/feed/FollowPackFeedScreen.kt | 4 +- .../geocaches/create/NewGeocacheScreen.kt | 2 +- .../geocaches/detail/GeocacheOwnerActions.kt | 2 +- .../screen/loggedIn/geohash/GeoHashScreen.kt | 2 +- .../loggedIn/gitRepo/GitRepositoryHomeUi.kt | 2 +- .../loggedIn/home/live/RenderGeohashBubble.kt | 2 +- .../loggedIn/keyBackup/AccountBackupScreen.kt | 2 +- .../napplets/ConnectedAppDetailScreen.kt | 4 +- .../loggedIn/napplets/ConnectedAppsScreen.kt | 17 +- .../screen/loggedIn/nests/NestsFeedLoaded.kt | 4 +- .../loggedIn/pictures/PictureCardCompose.kt | 2 +- .../podcasts/PodcastEpisodeListItem.kt | 2 +- .../screen/loggedIn/podcasts/PodcastHeader.kt | 2 +- .../authoring/EditPodcastShowScreen.kt | 2 +- .../loggedIn/privacy/PrivacyOptionsScreen.kt | 6 +- .../profile/header/PaymentTargetsDialog.kt | 2 +- .../screen/loggedIn/qrcode/QrCodeScanner.kt | 4 +- .../ui/screen/loggedIn/qrcode/ShowQRScreen.kt | 2 + .../qrcode/scanner/QrScannerDialog.kt | 10 +- .../relays/common/BasicRelaySetupInfo.kt | 4 +- .../relays/common/BasicRelaySetupInfoModel.kt | 3 +- .../connected/ConnectedRelayListViewModel.kt | 7 +- .../ActiveSubscriptionsScreen.kt | 8 +- .../loggedIn/settings/AllSettingsScreen.kt | 2 +- .../loggedIn/settings/AppSettingsScreen.kt | 143 ------- .../settings/BottomBarSettingsScreen.kt | 8 +- .../screen/loggedIn/settings/SettingsRows.kt | 183 +++++++++ .../settings/cordn/CordnMigrateScreen.kt | 2 +- .../settings/nip46/Nip46SignerScreen.kt | 2 +- .../loggedIn/shorts/VideoCardCompose.kt | 2 +- .../softwareapps/SoftwareAppDetailScreen.kt | 2 +- .../loggedIn/threadview/ThreadFeedView.kt | 2 +- .../loggedIn/video/FileMetadataCardCompose.kt | 2 +- .../loggedIn/wallet/OnchainZapSendDialog.kt | 12 +- .../loggedIn/wallet/WalletReceiveScreen.kt | 2 +- .../screen/loggedIn/wallet/WalletViewModel.kt | 12 +- .../vitorpamplona/amethyst/ui/theme/Theme.kt | 3 + .../commons/favorites/FavoriteCoordinate.kt | 39 ++ .../amethyst/commons/service/AppServices.kt | 51 +++ .../amethyst/commons/util/GroupedNumber.kt | 24 ++ .../viewmodels/AccountViewModelHost.kt | 11 + .../commons/util/GroupedNumber.ios.kt | 31 ++ .../commons/util/GroupedNumber.jvmAndroid.kt | 25 ++ commonsUI/build.gradle.kts | 4 + .../KeepScreenBrightAndAwake.android.kt | 12 +- .../composeResources/values-cs/strings.xml | 2 + .../composeResources/values-de/strings.xml | 2 + .../composeResources/values-eo/strings.xml | 2 + .../composeResources/values-es/strings.xml | 2 + .../composeResources/values-fa/strings.xml | 2 + .../composeResources/values-fr/strings.xml | 2 + .../composeResources/values-in/strings.xml | 2 + .../composeResources/values-ja/strings.xml | 2 + .../values-nl-rBE/strings.xml | 2 + .../composeResources/values-nl/strings.xml | 2 + .../composeResources/values-ru/strings.xml | 2 + .../composeResources/values-ta/strings.xml | 2 + .../composeResources/values-th/strings.xml | 2 + .../composeResources/values-tr/strings.xml | 2 + .../composeResources/values-uk/strings.xml | 2 + .../composeResources/values-zh/strings.xml | 2 + .../composeResources/values/strings.xml | 2 + .../commons/ui/platform/AppPlatform.kt | 97 +++++ .../commons/ui/platform/LocalAppServices.kt | 30 ++ .../qrcode/KeepScreenBrightAndAwake.kt | 34 ++ .../ui/screen/loggedIn/qrcode/QrCodeDrawer.kt | 109 +++-- .../ui/screen/loggedIn/qrcode/QrMatrix.kt | 39 ++ .../ui/screen/loggedIn/qrcode/ScanOutcome.kt | 30 ++ .../qrcode/KeepScreenBrightAndAwake.ios.kt | 27 ++ .../ui/screen/loggedIn/qrcode/QrMatrix.ios.kt | 24 ++ .../loggedIn/qrcode/QrMatrix.jvmAndroid.kt | 52 +++ .../AccountViewModelPreviews.jvmAndroid.kt | 9 + .../qrcode/KeepScreenBrightAndAwake.jvm.kt | 27 ++ .../__pycache__/fix_escapes.cpython-311.pyc | Bin 6042 -> 10670 bytes .../__pycache__/migrate.cpython-311.pyc | Bin 11113 -> 11113 bytes 112 files changed, 1540 insertions(+), 779 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/UriToRoute.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/QueryParameters.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppServices.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsRows.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/favorites/FavoriteCoordinate.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/AppServices.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.kt create mode 100644 commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.ios.kt create mode 100644 commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.jvmAndroid.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt => commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.android.kt (86%) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/LocalAppServices.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt (78%) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/ScanOutcome.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.ios.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.ios.kt create mode 100644 commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.jvmAndroid.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.jvm.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/favorites/FavoriteAppLauncher.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/favorites/FavoriteAppLauncher.kt index 45cfd84f05..6274bda285 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/favorites/FavoriteAppLauncher.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/favorites/FavoriteAppLauncher.kt @@ -27,6 +27,7 @@ import android.os.Bundle import android.widget.Toast import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp +import com.vitorpamplona.amethyst.commons.favorites.favoriteCoordinateOf import com.vitorpamplona.amethyst.commons.model.ThemeType import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.napplet.NappletLauncher @@ -212,17 +213,6 @@ object FavoriteAppLauncher { } } - /** - * The addressable coordinate `kind:pubkey:dtag` used to key an nsite/napplet favorite. Stored - * instead of the content hash so the favorite survives routine code/manifest updates. - */ - fun coordinateOf(event: Event): String { - val dTag = - when (event) { - is NamedNappletEvent -> event.identifier() - is NamedSiteEvent -> event.identifier() - else -> "" - } - return "${event.kind}:${event.pubKey}:$dTag" - } + /** See [favoriteCoordinateOf]. */ + fun coordinateOf(event: Event): String = favoriteCoordinateOf(event) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 33902c8bde..1e0a0202c3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -27,51 +27,19 @@ import androidx.activity.enableEdgeToEdge import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.amethyst.commons.actions.ConcordActions -import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.commons.model.cache.LocalCache -import com.vitorpamplona.amethyst.commons.model.navigation.Route -import com.vitorpamplona.amethyst.commons.model.navigation.routeFor -import com.vitorpamplona.amethyst.commons.richtext.RichTextParser -import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor -import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeForPointer import com.vitorpamplona.amethyst.commons.ui.note.elements.NowProvider import com.vitorpamplona.amethyst.debugState import com.vitorpamplona.amethyst.service.lang.LanguageTranslatorService import com.vitorpamplona.amethyst.service.notifications.NotificationRelayService -import com.vitorpamplona.amethyst.service.notifications.NotificationRoutes import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING import com.vitorpamplona.amethyst.service.playback.pip.BackgroundMedia -import com.vitorpamplona.amethyst.ui.navigation.findParameterValue -import com.vitorpamplona.amethyst.ui.navigation.findQueryParameterValue import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.theme.AmethystTheme -import com.vitorpamplona.quartz.buzz.invite.BuzzInviteLink -import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent -import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey -import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser -import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress -import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed -import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent -import com.vitorpamplona.quartz.nip19Bech32.entities.NNote -import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile -import com.vitorpamplona.quartz.nip19Bech32.entities.NPub -import com.vitorpamplona.quartz.nip29RelayGroups.GroupInviteLink -import com.vitorpamplona.quartz.nip29RelayGroups.GroupNAddrInvite -import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent -import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectURI -import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect -import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent -import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent -import com.vitorpamplona.quartz.nip73ExternalIds.urls.UrlId import com.vitorpamplona.quartz.utils.Log -import com.vitorpamplona.quartz.utils.UriParser -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import java.net.URLDecoder class MainActivity : AppCompatActivity() { companion object { @@ -155,339 +123,3 @@ class MainActivity : AppCompatActivity() { super.onDestroy() } } - -private const val NOSTR_URI_PREFIX = "nostr:" - -fun isNotificationRoute(uri: String) = uri.startsWith("notifications", true) || uri.startsWith("nostr:notifications", true) - -fun isHashtagRoute(uri: String) = uri.startsWith("hashtag?id=") || uri.startsWith("nostr:hashtag?id=") - -/** - * A markdown link target that is a bare hashtag fragment, e.g. the `#NostrMultiplayerGames` in - * `[Games](#NostrMultiplayerGames)`. Returns the tag without the leading `#`, or null when the - * uri is anything else (full URLs with anchors don't start with `#`). Reuses the same character - * class that linkifies #hashtags in plain text, so validity matches how tags parse everywhere else. - */ -fun fragmentHashtagOrNull(uri: String): String? { - val match = RichTextParser.hashTagsPattern.matchEntire(uri) ?: return null - if (!match.groups[2]?.value.isNullOrEmpty()) return null - return match.groups[1]?.value -} - -fun isUrlRoute(uri: String) = uri.startsWith("url?id=") || uri.startsWith("nostr:url?id=") - -/** - * A private chatroom, addressed by its participants rather than by a message. - * Posted by DM notifications — see [NotificationRoutes.chatroomUri] for why a DM - * cannot deep-link through its own note. - */ -fun isChatroomRoute(uri: String) = uri.startsWith("chatroom?id=") || uri.startsWith("nostr:chatroom?id=") - -/** - * Note what this deliberately does *not* do: register the room on the account. - * - * `routeToMessage` would, and that is right for the in-app callers, but wrong here twice over. - * `uriToRoute` runs against whichever account is current, *before* `?account=` is read and the - * switch happens (`AppNavigation.NavigateIfIntentRequested`), so a DM notification for account B - * tapped while A is on screen would insert an empty conversation into **A**'s message list. And - * `nostr:` is an exported, browsable scheme, so any web page could post - * `nostr:chatroom?id=&account=…` and inject a room of its choosing. - * - * Nothing needs it: `ChatroomFeedFilter.chatroom()` calls `getOrCreatePrivateChatroom` when the - * screen actually opens, by which point the switch has happened and the room lands on the right - * account. - * - * The ids are still checked here — a room key is a set of pubkeys, so anything that isn't one is - * not a room, and a bad deep link should resolve to nothing rather than to an unopenable screen. - */ -fun chatroomRoute(uri: String): Route? { - val users = - uri - .findQueryParameterValue("id") - ?.split(',') - ?.map { it.trim() } - ?.takeIf { it.isNotEmpty() && it.all(::isPubKeyHex) } - ?.toSet() ?: return null - - return Route.Room(ChatroomKey(users)) -} - -/** A bare 32-byte lowercase-hex pubkey, the only thing a chatroom key is made of. */ -private fun isPubKeyHex(value: String) = value.length == 64 && value.all { it in '0'..'9' || it in 'a'..'f' } - -/** - * A NIP-17 private note or reply, addressed by the rumor's own id because its `nevent` - * names the undeliverable gift wrap instead — see [NotificationRoutes.privateNoteUri]. - */ -fun isPrivateNoteRoute(uri: String) = uri.startsWith("privatenote?id=") || uri.startsWith("nostr:privatenote?id=") - -fun privateNoteRoute(uri: String): Route? { - val id = uri.findQueryParameterValue("id") ?: return null - return Route.EventRedirect(id, isPrivate = true) -} - -fun isConnectedAppRoute(uri: String) = uri.startsWith("connectedapp?coordinate=") || uri.startsWith("nostr:connectedapp?coordinate=") - -/** - * The Connected Apps permission-detail route for an app coordinate (`pubkey:dtag` for a napplet/nsite, - * `browser:` for a web client). Fired by the sandbox host so a running full-screen surface can - * jump straight to its editable permissions; the coordinate is URL-encoded into the [coordinate] param. - */ -fun connectedAppRoute(uri: String): Route.ConnectedAppDetail? { - val coordinate = - runCatching { - val raw = java.net.URI(uri.removePrefix(NOSTR_URI_PREFIX)).findParameterValue("coordinate") ?: return null - URLDecoder.decode(raw, Charsets.UTF_8.name()) - }.getOrNull()?.takeIf { it.isNotBlank() } ?: return null - - return Route.ConnectedAppDetail(coordinate) -} - -fun urlRoute(uri: String): Route.Url? { - val url = - runCatching { - val rawUrl = java.net.URI(uri.removePrefix(NOSTR_URI_PREFIX)).findParameterValue("id") ?: return null - URLDecoder.decode(rawUrl, Charsets.UTF_8.name()) - }.getOrNull() ?: return null - - return UrlId.toScopeOrNull(url)?.let { Route.Url(it) } -} - -fun isWalletConnectRoute(uri: String) = uri.startsWith("dlnwc?value=") || uri.startsWith("amethyst+walletconnect:dlnwc?value=") || uri.startsWith("amethyst+walletconnect://dlnwc?value=") - -fun isMarmotGroupRoute(uri: String) = uri.startsWith("marmot:") - -/** - * Tapping the always-on service notification. That notification's whole subject is the relay - * connections it is holding open, so it lands on the screen that explains them rather than on - * whatever tab happened to be last open. - */ -fun isActiveSubscriptionsRoute(uri: String) = uri.startsWith("activesubs", true) || uri.startsWith("nostr:activesubs", true) - -/** - * The launcher shortcut (res/xml/shortcuts.xml) fires `amethyst:scanqr`, caught by MainActivity's - * existing scheme filter. A pseudo-uri rather than a hardcoded component, because debug and - * benchmark builds carry an applicationId suffix a hardcoded targetPackage would miss, and - * manifest placeholders are not substituted into resource XML. - * - * `nostr:scanqr` is still accepted so an older pinned shortcut keeps working, but the shortcut - * itself uses our own scheme: an implicit `nostr:` VIEW intent can be answered by any Nostr client - * installed alongside us. - */ -fun isScanQrRoute(uri: String) = - uri.equals("scanqr", true) || - uri.equals("amethyst:scanqr", true) || - uri.equals("nostr:scanqr", true) - -private val MARMOT_HEX = Regex("^[0-9a-fA-F]+$") - -fun uriToRoute( - uri: String, - account: Account, -): Route? { - if (isNotificationRoute(uri)) { - val scrollTo = uri.findQueryParameterValue("scrollTo") - return Route.Notification(scrollToEventId = scrollTo) - } - if (isActiveSubscriptionsRoute(uri)) { - return Route.ActiveSubscriptions - } - if (isScanQrRoute(uri)) { - return Route.QRDisplay(account.signer.pubKey, startScanning = true) - } - if (isHashtagRoute(uri)) { - return Route.Hashtag(uri.removePrefix(NOSTR_URI_PREFIX).removePrefix("hashtag?id=").lowercase()) - } - fragmentHashtagOrNull(uri)?.let { - return Route.Hashtag(it.lowercase()) - } - if (isUrlRoute(uri)) { - return urlRoute(uri) - } - if (isChatroomRoute(uri)) { - return chatroomRoute(uri) - } - if (isPrivateNoteRoute(uri)) { - return privateNoteRoute(uri) - } - if (isConnectedAppRoute(uri)) { - return connectedAppRoute(uri) - } - - // A scanned/opened `nostrconnect://` offer is an app asking to connect to our signer: open the - // NIP-46 signer screen and let it run the pairing (it enables the signer as part of connecting). - if (uri.startsWith(NostrConnectURI.NOSTRCONNECT_SCHEME)) { - return Route.Nip46Signer(connectUri = uri) - } - - relayGroupInviteRoute(uri)?.let { return it } - concordInviteRoute(uri)?.let { return it } - buzzInviteRoute(uri)?.let { return it } - - val parsedNip19 = Nip19Parser.uriToRoute(uri) - val nip19 = parsedNip19?.entity - if (nip19 != null) { - LocalCache.consume(nip19) - - val route = - when (nip19) { - is NPub -> { - Route.Profile(nip19.hex) - } - - is NProfile -> { - Route.Profile(nip19.hex) - } - - is NNote -> { - Route.Note(nip19.hex) - } - - is NEvent -> { - val note = LocalCache.getOrCreateNote(nip19.hex) - // Only fall back to the pointer's own kind while the body is missing: once the - // event is cached it may belong somewhere the kind alone can't name (a channel, - // a chatroom), and routeFor knows that. - if (note.event == null) { - routeForPointer(nip19.kind, nip19.hex) ?: Route.EventRedirect(nip19.hex) - } else { - routeFor(note = note, loggedIn = account) ?: Route.EventRedirect(nip19.hex) - } - } - - is NAddress -> { - relayGroupDirectRoute(nip19, parsedNip19.additionalChars) - ?: routeFor( - note = LocalCache.getOrCreateAddressableNote(nip19.address()), - loggedIn = account, - ) ?: calendarDirectRoute(nip19) ?: Route.EventRedirect(nip19.aTag()) - } - - is NEmbed -> { - val noteEvent = nip19.event - if (noteEvent is AddressableEvent) { - routeFor( - note = LocalCache.getOrCreateAddressableNote(noteEvent.address()), - loggedIn = account, - ) ?: Route.EventRedirect(noteEvent.addressTag()) - } else { - routeFor( - note = LocalCache.getOrCreateNote(nip19.event.id), - loggedIn = account, - ) ?: Route.EventRedirect(nip19.event.id) - } - } - - else -> { - null - } - } - - if (route != null) { - return route - } - } - - if (isMarmotGroupRoute(uri)) { - // marmot:?account= - val groupHex = - uri - .removePrefix("marmot:") - .substringBefore("?") - .substringBefore("&") - if (groupHex.matches(MARMOT_HEX)) { - return Route.MarmotGroupChat(groupHex) - } - } - - if (isWalletConnectRoute(uri)) { - try { - val url = UriParser(uri) - val nip47Uri = url.getQueryParameter("value")?.firstOrNull() - if (nip47Uri != null) { - Nip47WalletConnect.parse(nip47Uri) - return Route.WalletAddNwc(nip47Uri) - } - } catch (e: Exception) { - if (e is CancellationException) throw e - } - } - - try { - Nip47WalletConnect.parse(uri) - return Route.WalletAddNwc(uri) - } catch (e: Exception) { - if (e is CancellationException) throw e - } - - return null -} - -/** - * Direct route for an `naddr` whose event hasn't arrived in [LocalCache] yet. When a notification - * is tapped (or a `nostr:naddr…` deep link arrives) for a calendar appointment we know is kind - * 31922/31923, route straight to the dedicated detail screen instead of bouncing through - * [Route.EventRedirect]. The detail screen issues its own per-event subscription, so the user - * sees the calendar-specific loading placeholder while the event arrives, not the generic - * redirect screen. - */ -private fun calendarDirectRoute(nip19: NAddress): Route? = - when (nip19.kind) { - CalendarTimeSlotEvent.KIND, - CalendarDateSlotEvent.KIND, - -> Route.CalendarEventDetail(nip19.kind, nip19.author, nip19.dTag) - else -> null - } - -/** - * Direct route for a NIP-29 group `naddr` (kind 39000) shared as a link. The group - * only exists on its host relay, so the naddr's relay hint is mandatory — the group - * id alone can't be resolved. With a hint we open the group chat straight away - * (cold start included); without one there's nowhere to look, so fall through. - */ -private fun relayGroupDirectRoute( - nip19: NAddress, - additionalChars: String?, -): Route? { - if (nip19.kind != GroupMetadataEvent.KIND) return null - val relay = nip19.relay.firstOrNull() ?: return null - // The spec allows an invite code appended as `naddr1…?invite=`; it arrives - // in the trailing chars after the bech32 body. Carry it so the group auto-joins. - val inviteCode = GroupNAddrInvite.parse(additionalChars) - return Route.RelayGroup(nip19.dTag, relay.url, inviteCode = inviteCode) -} - -/** - * Direct route for a NIP-29 group invite link in the de-facto `'[?code=]` - * form shared by Wisp and 0xchat (e.g. `wss://groups.0xchat.com'abc123`). The link carries its - * own host relay, so we can open the group straight away. Returns null for anything that isn't - * this exact shape, so it's safe to try before the nostr-entity parser. - */ -private fun relayGroupInviteRoute(uri: String): Route? { - val link = GroupInviteLink.parse(uri.removePrefix(NOSTR_URI_PREFIX)) ?: return null - return Route.RelayGroup(link.groupId, link.relayUrl.url, inviteCode = link.code) -} - -/** - * A shared Concord invite URL (`…/invite/#`). Cheap substring gates - * keep the parse off the hot path; the whole URL (fragment included) is carried into - * the route so the redeem flow still has the unlock token. - */ -private fun concordInviteRoute(uri: String): Route? = - if (uri.contains("/invite/") && uri.contains('#') && ConcordActions.parseInviteLink(uri) != null) { - Route.ConcordInvite(uri) - } else { - null - } - -/** - * A Buzz workspace invite (`https:///invite/`) — a plain `/invite/` https URL with - * no fragment, so disjoint from the Concord shape above. Opens the in-app join flow - * ([Route.BuzzInvite]) instead of the external browser so the claim signs with the user's key. - */ -private fun buzzInviteRoute(uri: String): Route? = - if (uri.contains("/invite/") && BuzzInviteLink.parse(uri) != null) { - Route.BuzzInvite(uri) - } else { - null - } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/UriToRoute.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/UriToRoute.kt new file mode 100644 index 0000000000..245498a847 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/UriToRoute.kt @@ -0,0 +1,388 @@ +/* + * 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 + +import com.vitorpamplona.amethyst.commons.actions.ConcordActions +import com.vitorpamplona.amethyst.commons.model.Account +import com.vitorpamplona.amethyst.commons.model.cache.LocalCache +import com.vitorpamplona.amethyst.commons.model.navigation.Route +import com.vitorpamplona.amethyst.commons.model.navigation.routeFor +import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor +import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeForPointer +import com.vitorpamplona.amethyst.ui.navigation.findQueryParameterValue +import com.vitorpamplona.quartz.buzz.invite.BuzzInviteLink +import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent +import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey +import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser +import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress +import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed +import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent +import com.vitorpamplona.quartz.nip19Bech32.entities.NNote +import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile +import com.vitorpamplona.quartz.nip19Bech32.entities.NPub +import com.vitorpamplona.quartz.nip29RelayGroups.GroupInviteLink +import com.vitorpamplona.quartz.nip29RelayGroups.GroupNAddrInvite +import com.vitorpamplona.quartz.nip29RelayGroups.metadata.GroupMetadataEvent +import com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectURI +import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect +import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent +import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent +import com.vitorpamplona.quartz.nip73ExternalIds.urls.UrlId +import com.vitorpamplona.quartz.utils.UriParser +import com.vitorpamplona.quartz.utils.UrlEncoder +import kotlinx.coroutines.CancellationException + +private const val NOSTR_URI_PREFIX = "nostr:" + +fun isNotificationRoute(uri: String) = uri.startsWith("notifications", true) || uri.startsWith("nostr:notifications", true) + +fun isHashtagRoute(uri: String) = uri.startsWith("hashtag?id=") || uri.startsWith("nostr:hashtag?id=") + +/** + * A markdown link target that is a bare hashtag fragment, e.g. the `#NostrMultiplayerGames` in + * `[Games](#NostrMultiplayerGames)`. Returns the tag without the leading `#`, or null when the + * uri is anything else (full URLs with anchors don't start with `#`). Reuses the same character + * class that linkifies #hashtags in plain text, so validity matches how tags parse everywhere else. + */ +fun fragmentHashtagOrNull(uri: String): String? { + val match = RichTextParser.hashTagsPattern.matchEntire(uri) ?: return null + if (!match.groups[2]?.value.isNullOrEmpty()) return null + return match.groups[1]?.value +} + +fun isUrlRoute(uri: String) = uri.startsWith("url?id=") || uri.startsWith("nostr:url?id=") + +/** + * A private chatroom, addressed by its participants rather than by a message. + * Posted by DM notifications — see `NotificationRoutes.chatroomUri` for why a DM + * cannot deep-link through its own note. + */ +fun isChatroomRoute(uri: String) = uri.startsWith("chatroom?id=") || uri.startsWith("nostr:chatroom?id=") + +/** + * Note what this deliberately does *not* do: register the room on the account. + * + * `routeToMessage` would, and that is right for the in-app callers, but wrong here twice over. + * `uriToRoute` runs against whichever account is current, *before* `?account=` is read and the + * switch happens (`AppNavigation.NavigateIfIntentRequested`), so a DM notification for account B + * tapped while A is on screen would insert an empty conversation into **A**'s message list. And + * `nostr:` is an exported, browsable scheme, so any web page could post + * `nostr:chatroom?id=&account=…` and inject a room of its choosing. + * + * Nothing needs it: `ChatroomFeedFilter.chatroom()` calls `getOrCreatePrivateChatroom` when the + * screen actually opens, by which point the switch has happened and the room lands on the right + * account. + * + * The ids are still checked here — a room key is a set of pubkeys, so anything that isn't one is + * not a room, and a bad deep link should resolve to nothing rather than to an unopenable screen. + */ +fun chatroomRoute(uri: String): Route? { + val users = + uri + .findQueryParameterValue("id") + ?.split(',') + ?.map { it.trim() } + ?.takeIf { it.isNotEmpty() && it.all(::isPubKeyHex) } + ?.toSet() ?: return null + + return Route.Room(ChatroomKey(users)) +} + +/** A bare 32-byte lowercase-hex pubkey, the only thing a chatroom key is made of. */ +private fun isPubKeyHex(value: String) = value.length == 64 && value.all { it in '0'..'9' || it in 'a'..'f' } + +/** + * A NIP-17 private note or reply, addressed by the rumor's own id because its `nevent` + * names the undeliverable gift wrap instead — see `NotificationRoutes.privateNoteUri`. + */ +fun isPrivateNoteRoute(uri: String) = uri.startsWith("privatenote?id=") || uri.startsWith("nostr:privatenote?id=") + +fun privateNoteRoute(uri: String): Route? { + val id = uri.findQueryParameterValue("id") ?: return null + return Route.EventRedirect(id, isPrivate = true) +} + +fun isConnectedAppRoute(uri: String) = uri.startsWith("connectedapp?coordinate=") || uri.startsWith("nostr:connectedapp?coordinate=") + +/** + * The Connected Apps permission-detail route for an app coordinate (`pubkey:dtag` for a napplet/nsite, + * `browser:` for a web client). Fired by the sandbox host so a running full-screen surface can + * jump straight to its editable permissions; the coordinate is URL-encoded into the [coordinate] param. + */ +fun connectedAppRoute(uri: String): Route.ConnectedAppDetail? { + val coordinate = + runCatching { + val raw = uri.removePrefix(NOSTR_URI_PREFIX).findQueryParameterValue("coordinate") ?: return null + UrlEncoder.decode(raw) + }.getOrNull()?.takeIf { it.isNotBlank() } ?: return null + + return Route.ConnectedAppDetail(coordinate) +} + +fun urlRoute(uri: String): Route.Url? { + val url = + runCatching { + val rawUrl = uri.removePrefix(NOSTR_URI_PREFIX).findQueryParameterValue("id") ?: return null + UrlEncoder.decode(rawUrl) + }.getOrNull() ?: return null + + return UrlId.toScopeOrNull(url)?.let { Route.Url(it) } +} + +fun isWalletConnectRoute(uri: String) = uri.startsWith("dlnwc?value=") || uri.startsWith("amethyst+walletconnect:dlnwc?value=") || uri.startsWith("amethyst+walletconnect://dlnwc?value=") + +fun isMarmotGroupRoute(uri: String) = uri.startsWith("marmot:") + +/** + * Tapping the always-on service notification. That notification's whole subject is the relay + * connections it is holding open, so it lands on the screen that explains them rather than on + * whatever tab happened to be last open. + */ +fun isActiveSubscriptionsRoute(uri: String) = uri.startsWith("activesubs", true) || uri.startsWith("nostr:activesubs", true) + +/** + * The launcher shortcut (res/xml/shortcuts.xml) fires `amethyst:scanqr`, caught by MainActivity's + * existing scheme filter. A pseudo-uri rather than a hardcoded component, because debug and + * benchmark builds carry an applicationId suffix a hardcoded targetPackage would miss, and + * manifest placeholders are not substituted into resource XML. + * + * `nostr:scanqr` is still accepted so an older pinned shortcut keeps working, but the shortcut + * itself uses our own scheme: an implicit `nostr:` VIEW intent can be answered by any Nostr client + * installed alongside us. + */ +fun isScanQrRoute(uri: String) = + uri.equals("scanqr", true) || + uri.equals("amethyst:scanqr", true) || + uri.equals("nostr:scanqr", true) + +private val MARMOT_HEX = Regex("^[0-9a-fA-F]+$") + +fun uriToRoute( + uri: String, + account: Account, +): Route? { + if (isNotificationRoute(uri)) { + val scrollTo = uri.findQueryParameterValue("scrollTo") + return Route.Notification(scrollToEventId = scrollTo) + } + if (isActiveSubscriptionsRoute(uri)) { + return Route.ActiveSubscriptions + } + if (isScanQrRoute(uri)) { + return Route.QRDisplay(account.signer.pubKey, startScanning = true) + } + if (isHashtagRoute(uri)) { + return Route.Hashtag(uri.removePrefix(NOSTR_URI_PREFIX).removePrefix("hashtag?id=").lowercase()) + } + fragmentHashtagOrNull(uri)?.let { + return Route.Hashtag(it.lowercase()) + } + if (isUrlRoute(uri)) { + return urlRoute(uri) + } + if (isChatroomRoute(uri)) { + return chatroomRoute(uri) + } + if (isPrivateNoteRoute(uri)) { + return privateNoteRoute(uri) + } + if (isConnectedAppRoute(uri)) { + return connectedAppRoute(uri) + } + + // A scanned/opened `nostrconnect://` offer is an app asking to connect to our signer: open the + // NIP-46 signer screen and let it run the pairing (it enables the signer as part of connecting). + if (uri.startsWith(NostrConnectURI.NOSTRCONNECT_SCHEME)) { + return Route.Nip46Signer(connectUri = uri) + } + + relayGroupInviteRoute(uri)?.let { return it } + concordInviteRoute(uri)?.let { return it } + buzzInviteRoute(uri)?.let { return it } + + val parsedNip19 = Nip19Parser.uriToRoute(uri) + val nip19 = parsedNip19?.entity + if (nip19 != null) { + LocalCache.consume(nip19) + + val route = + when (nip19) { + is NPub -> { + Route.Profile(nip19.hex) + } + + is NProfile -> { + Route.Profile(nip19.hex) + } + + is NNote -> { + Route.Note(nip19.hex) + } + + is NEvent -> { + val note = LocalCache.getOrCreateNote(nip19.hex) + // Only fall back to the pointer's own kind while the body is missing: once the + // event is cached it may belong somewhere the kind alone can't name (a channel, + // a chatroom), and routeFor knows that. + if (note.event == null) { + routeForPointer(nip19.kind, nip19.hex) ?: Route.EventRedirect(nip19.hex) + } else { + routeFor(note = note, loggedIn = account) ?: Route.EventRedirect(nip19.hex) + } + } + + is NAddress -> { + relayGroupDirectRoute(nip19, parsedNip19.additionalChars) + ?: routeFor( + note = LocalCache.getOrCreateAddressableNote(nip19.address()), + loggedIn = account, + ) ?: calendarDirectRoute(nip19) ?: Route.EventRedirect(nip19.aTag()) + } + + is NEmbed -> { + val noteEvent = nip19.event + if (noteEvent is AddressableEvent) { + routeFor( + note = LocalCache.getOrCreateAddressableNote(noteEvent.address()), + loggedIn = account, + ) ?: Route.EventRedirect(noteEvent.addressTag()) + } else { + routeFor( + note = LocalCache.getOrCreateNote(nip19.event.id), + loggedIn = account, + ) ?: Route.EventRedirect(nip19.event.id) + } + } + + else -> { + null + } + } + + if (route != null) { + return route + } + } + + if (isMarmotGroupRoute(uri)) { + // marmot:?account= + val groupHex = + uri + .removePrefix("marmot:") + .substringBefore("?") + .substringBefore("&") + if (groupHex.matches(MARMOT_HEX)) { + return Route.MarmotGroupChat(groupHex) + } + } + + if (isWalletConnectRoute(uri)) { + try { + val url = UriParser(uri) + val nip47Uri = url.getQueryParameter("value")?.firstOrNull() + if (nip47Uri != null) { + Nip47WalletConnect.parse(nip47Uri) + return Route.WalletAddNwc(nip47Uri) + } + } catch (e: Exception) { + if (e is CancellationException) throw e + } + } + + try { + Nip47WalletConnect.parse(uri) + return Route.WalletAddNwc(uri) + } catch (e: Exception) { + if (e is CancellationException) throw e + } + + return null +} + +/** + * Direct route for an `naddr` whose event hasn't arrived in [LocalCache] yet. When a notification + * is tapped (or a `nostr:naddr…` deep link arrives) for a calendar appointment we know is kind + * 31922/31923, route straight to the dedicated detail screen instead of bouncing through + * [Route.EventRedirect]. The detail screen issues its own per-event subscription, so the user + * sees the calendar-specific loading placeholder while the event arrives, not the generic + * redirect screen. + */ +private fun calendarDirectRoute(nip19: NAddress): Route? = + when (nip19.kind) { + CalendarTimeSlotEvent.KIND, + CalendarDateSlotEvent.KIND, + -> Route.CalendarEventDetail(nip19.kind, nip19.author, nip19.dTag) + else -> null + } + +/** + * Direct route for a NIP-29 group `naddr` (kind 39000) shared as a link. The group + * only exists on its host relay, so the naddr's relay hint is mandatory — the group + * id alone can't be resolved. With a hint we open the group chat straight away + * (cold start included); without one there's nowhere to look, so fall through. + */ +private fun relayGroupDirectRoute( + nip19: NAddress, + additionalChars: String?, +): Route? { + if (nip19.kind != GroupMetadataEvent.KIND) return null + val relay = nip19.relay.firstOrNull() ?: return null + // The spec allows an invite code appended as `naddr1…?invite=`; it arrives + // in the trailing chars after the bech32 body. Carry it so the group auto-joins. + val inviteCode = GroupNAddrInvite.parse(additionalChars) + return Route.RelayGroup(nip19.dTag, relay.url, inviteCode = inviteCode) +} + +/** + * Direct route for a NIP-29 group invite link in the de-facto `'[?code=]` + * form shared by Wisp and 0xchat (e.g. `wss://groups.0xchat.com'abc123`). The link carries its + * own host relay, so we can open the group straight away. Returns null for anything that isn't + * this exact shape, so it's safe to try before the nostr-entity parser. + */ +private fun relayGroupInviteRoute(uri: String): Route? { + val link = GroupInviteLink.parse(uri.removePrefix(NOSTR_URI_PREFIX)) ?: return null + return Route.RelayGroup(link.groupId, link.relayUrl.url, inviteCode = link.code) +} + +/** + * A shared Concord invite URL (`…/invite/#`). Cheap substring gates + * keep the parse off the hot path; the whole URL (fragment included) is carried into + * the route so the redeem flow still has the unlock token. + */ +private fun concordInviteRoute(uri: String): Route? = + if (uri.contains("/invite/") && uri.contains('#') && ConcordActions.parseInviteLink(uri) != null) { + Route.ConcordInvite(uri) + } else { + null + } + +/** + * A Buzz workspace invite (`https:///invite/`) — a plain `/invite/` https URL with + * no fragment, so disjoint from the Concord shape above. Opens the in-app join flow + * ([Route.BuzzInvite]) instead of the external browser so the claim signs with the user's key. + */ +private fun buzzInviteRoute(uri: String): Route? = + if (uri.contains("/invite/") && BuzzInviteLink.parse(uri) != null) { + Route.BuzzInvite(uri) + } else { + null + } 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 2531c35626..df27382ddf 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 @@ -1337,34 +1337,3 @@ fun URI.findParameterValue(parameterName: String): String? = Pair(name, value) }?.firstOrNull { it.first == parameterName } ?.second - -/** - * The value of [parameterName] in this URI's query string, without going through - * [java.net.URI]. - * - * Needed because `java.net.URI` only exposes `rawQuery` for *hierarchical* URIs. A URI - * with a scheme and no `//` is **opaque** — everything after the colon is one - * scheme-specific part — so `URI("marmot:?account=npub1…").rawQuery` is null. That - * is the shape [com.vitorpamplona.amethyst.service.notifications.NotificationRoutes.marmotUri] - * produces, so every Marmot group notification silently lost its `?account=` and opened - * the group under whichever account happened to be current instead of switching first. - * - * Splitting on the first `?` gets the same answer for both shapes, and returns null for a - * bare `nevent1…` with no query at all. - * - * [com.vitorpamplona.quartz.utils.UriParser] reads an opaque query correctly too, and is the - * right tool when a URI is already known to be well-formed. It is not this one: it builds a - * [java.net.URI], which *throws* on anything that is not a legal URI. What arrives here comes - * from an exported, browsable scheme, so it can be any string at all, and every caller on the - * deep-link path treats an unreadable uri as "no route" rather than as a crash. - */ -fun String.findQueryParameterValue(parameterName: String): String? { - val query = substringAfter('?', "") - if (query.isEmpty()) return null - - return query - .split('&') - .firstOrNull { it.substringBefore('=') == parameterName } - ?.substringAfter('=', "") - ?.ifEmpty { null } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/QueryParameters.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/QueryParameters.kt new file mode 100644 index 0000000000..d9699b7c70 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/QueryParameters.kt @@ -0,0 +1,52 @@ +/* + * 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.navigation + +/** + * The value of [parameterName] in this URI's query string, without going through + * `java.net.URI`. + * + * Needed because `java.net.URI` only exposes `rawQuery` for *hierarchical* URIs. A URI + * with a scheme and no `//` is **opaque** — everything after the colon is one + * scheme-specific part — so `URI("marmot:?account=npub1…").rawQuery` is null. That + * is the shape `NotificationRoutes.marmotUri` + * produces, so every Marmot group notification silently lost its `?account=` and opened + * the group under whichever account happened to be current instead of switching first. + * + * Splitting on the first `?` gets the same answer for both shapes, and returns null for a + * bare `nevent1…` with no query at all. + * + * `UriParser` reads an opaque query correctly too, and is the + * right tool when a URI is already known to be well-formed. It is not this one: it builds a + * `java.net.URI`, which *throws* on anything that is not a legal URI. What arrives here comes + * from an exported, browsable scheme, so it can be any string at all, and every caller on the + * deep-link path treats an unreadable uri as "no route" rather than as a crash. + */ +fun String.findQueryParameterValue(parameterName: String): String? { + val query = substringAfter('?', "") + if (query.isEmpty()) return null + + return query + .split('&') + .firstOrNull { it.substringBefore('=') == parameterName } + ?.substringAfter('=', "") + ?.ifEmpty { null } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppBottomBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppBottomBar.kt index 6b17157a0b..bf0cacf966 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppBottomBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppBottomBar.kt @@ -43,7 +43,6 @@ import androidx.compose.ui.Alignment 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.browser.OmniboxInput import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon @@ -55,6 +54,8 @@ import com.vitorpamplona.amethyst.commons.ui.insets.keyboardAsState import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.LocalTabReselectCoordinator import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices +import com.vitorpamplona.amethyst.commons.ui.platform.rememberNappletIconModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding @@ -64,7 +65,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size25Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size27Modifier import com.vitorpamplona.amethyst.commons.ui.theme.onSurface65 import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.rememberNappletIconModel /** Content height of the [AppBottomBar] (the 50.dp Column inside [RenderBottomMenu]), * exclusive of the system navigation-bar inset. Used by FAB callers that want to @@ -114,7 +114,7 @@ fun AppBottomBar( // Favorite entries in the unified list resolve to a live favorite for their icon/label and to an // embedded-tab route. Both kinds embed in-process (WebApp → browser surface, NostrApp → napplet // surface), so such a tab swaps in place rather than launching an activity from the bottom row. - val favorites by Amethyst.instance.favoriteApps.favorites + val favorites by LocalAppServices.current.favoriteApps.favorites .collectAsStateWithLifecycle() val isKeyboardState by keyboardAsState() @@ -134,10 +134,11 @@ internal fun rememberFavoriteIconModel(fav: FavoriteApp): Any? = when (fav) { is FavoriteApp.WebApp -> { // Captured favicons, keyed so the icon appears once the site's capture lands. - val iconKeys by Amethyst.instance.browserIcons.keys + val browserIcons = LocalAppServices.current.browserIcons + val iconKeys by browserIcons.keys .collectAsStateWithLifecycle() remember(fav, iconKeys) { - OmniboxInput.hostOf(fav.url)?.let(Amethyst.instance.browserIcons::iconModelFor) + OmniboxInput.hostOf(fav.url)?.let(browserIcons::iconModelFor) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/GroupBottomBarEntries.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/GroupBottomBarEntries.kt index 570e5518e9..bd252a75eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/GroupBottomBarEntries.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/GroupBottomBarEntries.kt @@ -44,10 +44,10 @@ import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.channel.Channel import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.concord_home_title import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage +import com.vitorpamplona.amethyst.commons.ui.platform.rememberConcordImageModel import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl import com.vitorpamplona.quartz.nip29RelayGroups.GroupId diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/AroundMeLocationLabel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/AroundMeLocationLabel.kt index 9ebc663f23..dae3a52c12 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/AroundMeLocationLabel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/AroundMeLocationLabel.kt @@ -40,10 +40,10 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.lack_location_permissions import com.vitorpamplona.amethyst.commons.resources.loading_location import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Font12SP import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName /** The "around me" feed's subtitle: asks for location, then shows the city it resolved to. */ @OptIn(ExperimentalPermissionsApi::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index 387ff21249..492cf11b65 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -95,6 +95,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.AddZapraise import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.ZapRaiserRequest import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.ForwardZapToButton import com.vitorpamplona.amethyst.commons.ui.note.nip22Comments.DisplayExternalId +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.note.types.ReplyRenderType import com.vitorpamplona.amethyst.commons.ui.platform.GeohashLocationPickerDialog import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -118,7 +119,6 @@ import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoad import com.vitorpamplona.amethyst.ui.note.creators.expiration.ExpirationDatePicker import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest import com.vitorpamplona.amethyst.ui.note.creators.location.GeoHashPostSection -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.amethyst.ui.note.creators.messagefield.MessageField import com.vitorpamplona.amethyst.ui.note.creators.notify.AudienceFlap import com.vitorpamplona.amethyst.ui.note.creators.notify.AudienceSheet diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt index 9044bcecb9..0932556399 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsQrScreen.kt @@ -61,10 +61,10 @@ import com.vitorpamplona.amethyst.commons.resources.share_as_qr_mode_web import com.vitorpamplona.amethyst.commons.ui.components.LoadNote import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.KeepScreenBrightAndAwake +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.KeepScreenBrightAndAwake -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer // A cap, not a fixed size: QrCodeDrawer's own quiet zone (QR_QUIET_ZONE_MODULES in QrCodeDrawer.kt) is a // fixed pixel count subtracted from raw size.width, so its share of the tile grows as density diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/StaticWebsite.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/StaticWebsite.kt index f8c8cf0b36..093a411218 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/StaticWebsite.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/StaticWebsite.kt @@ -24,11 +24,11 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.platform.LocalContext import com.vitorpamplona.amethyst.Amethyst +import com.vitorpamplona.amethyst.commons.favorites.favoriteCoordinateOf import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.StaticWebsiteCard import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.amethyst.napplet.NappletLauncher import com.vitorpamplona.amethyst.napplethost.HostProfile import com.vitorpamplona.amethyst.napplethost.NappletBlobPrefetcher @@ -68,7 +68,7 @@ fun RenderRootNappletEvent( } else { null }, - headerActions = { FavoriteToggleButton(FavoriteAppLauncher.coordinateOf(event), event.title() ?: "Napplet", event.icon()) }, + headerActions = { FavoriteToggleButton(favoriteCoordinateOf(event), event.title() ?: "Napplet", event.icon()) }, ) } @@ -98,7 +98,7 @@ fun RenderNamedNappletEvent( } else { null }, - headerActions = { FavoriteToggleButton(FavoriteAppLauncher.coordinateOf(event), event.title() ?: event.identifier(), event.icon()) }, + headerActions = { FavoriteToggleButton(favoriteCoordinateOf(event), event.title() ?: event.identifier(), event.icon()) }, ) } @@ -139,7 +139,7 @@ fun RenderRootSiteEvent( } else { null }, - headerActions = { FavoriteToggleButton(FavoriteAppLauncher.coordinateOf(event), event.title() ?: "nsite", event.icon()) }, + headerActions = { FavoriteToggleButton(favoriteCoordinateOf(event), event.title() ?: "nsite", event.icon()) }, ) } @@ -180,7 +180,7 @@ fun RenderNamedSiteEvent( } else { null }, - headerActions = { FavoriteToggleButton(FavoriteAppLauncher.coordinateOf(event), event.title() ?: event.identifier(), event.icon()) }, + headerActions = { FavoriteToggleButton(favoriteCoordinateOf(event), event.title() ?: event.identifier(), event.icon()) }, ) } 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 47ad8b75a9..6c46a7b4af 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 @@ -20,18 +20,31 @@ */ package com.vitorpamplona.amethyst.ui.platform +import android.content.Context import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.platform.AppLauncher import com.vitorpamplona.amethyst.commons.ui.platform.AppPlatform +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ScanOutcome import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel +import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.amethyst.ui.navigation.topbars.AndroidAroundMeLocationLabel +import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer +import com.vitorpamplona.amethyst.favorites.rememberManifestIconModel as AppRememberManifestIconModel +import com.vitorpamplona.amethyst.favorites.rememberNappletIconModel as AppRememberNappletIconModel +import com.vitorpamplona.amethyst.favorites.rememberWebAppIconModel as AppRememberWebAppIconModel import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar as AppBottomBarImpl import com.vitorpamplona.amethyst.ui.note.creators.location.GeohashLocationPickerContent as AppGeohashLocationPickerContent import com.vitorpamplona.amethyst.ui.note.creators.location.GeohashLocationPickerDialog as AppGeohashLocationPickerDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel as AppRememberConcordImageModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.QrCodeScannerDialog as AppQrCodeScannerDialog -/** Android's [AppPlatform]: the app's own bottom bar and location label. */ +/** Android's [AppPlatform]: the app's own shell pieces, camera scanner, app launcher and icon caches. */ object AndroidAppPlatform : AppPlatform { @Composable override fun AppBottomBar( @@ -58,4 +71,48 @@ object AndroidAppPlatform : AppPlatform { onConfirm: (String) -> Unit, modifier: Modifier, ) = AppGeohashLocationPickerContent(initialGeohash, confirmLabel, onConfirm, modifier) + + @Composable + override fun QrCodeScannerDialog( + onDismiss: () -> Unit, + onScan: (String) -> ScanOutcome, + ) = AppQrCodeScannerDialog(onDismiss, onScan) + + @Composable + override fun rememberAppLauncher(): AppLauncher { + val context = LocalContext.current + return remember(context) { AndroidAppLauncher(context) } + } + + @Composable + override fun rememberConcordImageModel( + pointer: ImagePointer?, + accountViewModel: AccountViewModel, + ): String? = AppRememberConcordImageModel(pointer, accountViewModel) + + @Composable + override fun rememberNappletIconModel(coordinate: String): String? = AppRememberNappletIconModel(coordinate) + + @Composable + override fun rememberManifestIconModel( + author: String, + identifier: String, + ): String? = AppRememberManifestIconModel(author, identifier) + + @Composable + override fun rememberWebAppIconModel(url: String): String? = AppRememberWebAppIconModel(url) +} + +private class AndroidAppLauncher( + private val context: Context, +) : AppLauncher { + override fun launch( + app: FavoriteApp, + stillLoading: String, + ) = FavoriteAppLauncher.launch(context, app, stillLoading) + + override fun launchUrl( + url: String, + preferTor: Boolean, + ) = FavoriteAppLauncher.launchUrl(context, url, preferTor) } 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 new file mode 100644 index 0000000000..8538e98df4 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppServices.kt @@ -0,0 +1,49 @@ +/* + * 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 com.vitorpamplona.amethyst.Amethyst +import com.vitorpamplona.amethyst.commons.browser.BrowserHistoryRegistry +import com.vitorpamplona.amethyst.commons.browser.BrowserIconRegistry +import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionStore +import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppsRegistry +import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionLedger +import com.vitorpamplona.amethyst.commons.service.AppServices +import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow + +/** + * [AppServices] over the main process's app modules. Every member is a getter, so installing + * this object costs nothing and is safe in the `:napplet` process, where `Amethyst.instance` is + * never built, as long as nothing there reads it. + */ +object AndroidAppServices : AppServices { + override val favoriteApps: FavoriteAppsRegistry get() = Amethyst.instance.favoriteApps + + override val browserHistory: BrowserHistoryRegistry get() = Amethyst.instance.browserHistory + + override val browserIcons: BrowserIconRegistry get() = Amethyst.instance.browserIcons + + override val nappletPermissionLedger: NappletPermissionLedger get() = Amethyst.instance.nappletPermissionLedger + + override val signerPermissionStore: NostrSignerPermissionStore get() = Amethyst.instance.signerPermissionStore + + override val torSettings: TorSettingsFlow get() = Amethyst.instance.torPrefs.value +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 953e7058cf..7f01e6ba76 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -100,24 +100,24 @@ open class UserFeedViewModel( } init { - Log.d("Init") { "${this.javaClass.simpleName}" } + Log.d("Init") { "${this::class.simpleName}" } viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes -> - Log.d("Rendering Metrics") { "Update feeds: ${this@UserFeedViewModel.javaClass.simpleName} with ${newNotes.size}" } + Log.d("Rendering Metrics") { "Update feeds: ${this@UserFeedViewModel::class.simpleName} with ${newNotes.size}" } invalidateData() } } viewModelScope.launch(Dispatchers.IO) { LocalCache.live.deletedEventBundles.collect { newNotes -> - Log.d("Rendering Metrics") { "Delete from feeds: ${this@UserFeedViewModel.javaClass.simpleName} with ${newNotes.size}" } + Log.d("Rendering Metrics") { "Delete from feeds: ${this@UserFeedViewModel::class.simpleName} with ${newNotes.size}" } invalidateData() } } } override fun onCleared() { - Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" } + Log.d("Init") { "OnCleared: ${this::class.simpleName}" } bundler.cancel() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt index a3f5135e7d..310308d505 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt @@ -29,16 +29,21 @@ import com.vitorpamplona.amethyst.commons.service.lnurl.LnurlHttpTransport import com.vitorpamplona.amethyst.commons.service.lnurl.OkHttpLnurlTransport import com.vitorpamplona.amethyst.commons.service.pow.PoWJobFailure import com.vitorpamplona.amethyst.commons.tor.MoneyOpRelayRouting +import com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModelHost import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.dismissNotificationForEvent import com.vitorpamplona.amethyst.ui.note.payViaIntent import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip01Core.relay.client.auth.RelayAuthSnapshot import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilder import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes +import kotlinx.collections.immutable.PersistentMap import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart @@ -60,6 +65,11 @@ class AndroidAccountViewModelHost( override val moneyOpRelays: MoneyOpRelayRouting get() = modules.torEvaluatorFlow + override val torRelayEvaluation: StateFlow get() = modules.torEvaluatorFlow.flow + + override val relayAuthState: StateFlow> + get() = modules.authCoordinator.receiver.authStateFlow + override val savedAccounts: Flow> = LocalPreferences .accountsFlow() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt index a081aa07f3..dda3ca3122 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt @@ -142,13 +142,13 @@ import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.commons.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.LoadRelayGroupChannel +import com.vitorpamplona.amethyst.commons.ui.stringRes 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.ui.pluralStringRes -import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.diff.ContentChange import com.vitorpamplona.quartz.nip01Core.diff.ValueChange diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt index eba5ffc5f3..a6a6c76d64 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt @@ -117,12 +117,12 @@ import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.commons.ui.note.LoadPublicChatChannel import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.stringRes 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.ui.note.creators.location.LoadCityName import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListDiff import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListDiff diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt index 9980be0f5d..f807dba8d9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt @@ -114,7 +114,7 @@ import com.vitorpamplona.amethyst.commons.resources.backup_type_trust_providers import com.vitorpamplona.amethyst.commons.resources.backup_type_trust_providers_explainer import com.vitorpamplona.amethyst.commons.resources.backup_type_trusted_relays import com.vitorpamplona.amethyst.commons.resources.backup_type_trusted_relays_explainer -import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListDiff import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListDiff diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt index 725fd312a9..f3219d3d6d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt @@ -63,7 +63,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.text.TextRange import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction @@ -74,7 +73,6 @@ 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.browser.BrowserHistoryEntry import com.vitorpamplona.amethyst.commons.browser.DefaultWebClients import com.vitorpamplona.amethyst.commons.browser.OmniboxInput @@ -82,6 +80,7 @@ import com.vitorpamplona.amethyst.commons.browser.OmniboxSuggestions import com.vitorpamplona.amethyst.commons.browser.SuggestedWebApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon +import com.vitorpamplona.amethyst.commons.favorites.favoriteCoordinateOf import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.icons.symbols.rememberMaterialSymbolPainter @@ -89,6 +88,7 @@ import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.browser_address_hint import com.vitorpamplona.amethyst.commons.resources.browser_clear import com.vitorpamplona.amethyst.commons.resources.browser_discover_napplets import com.vitorpamplona.amethyst.commons.resources.browser_discover_nsites @@ -104,21 +104,21 @@ import com.vitorpamplona.amethyst.commons.resources.favorite_app_still_loading import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices +import com.vitorpamplona.amethyst.commons.ui.platform.rememberAppLauncher +import com.vitorpamplona.amethyst.commons.ui.platform.rememberNappletIconModel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.nsites.datasource.NsitesFilterAssemblerSubscription +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.amethyst.favorites.PreloadFavoriteNostrApps -import com.vitorpamplona.amethyst.favorites.rememberNappletIconModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.favoriteAppItems import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.datasource.NappletsFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip5aStaticWebsites.NamedSiteEvent import com.vitorpamplona.quartz.nip5aStaticWebsites.RootSiteEvent import com.vitorpamplona.quartz.nip5dNapplets.NamedNappletEvent import com.vitorpamplona.quartz.nip5dNapplets.RootNappletEvent import org.jetbrains.compose.resources.StringResource -import com.vitorpamplona.amethyst.commons.R as CommonsR /** How many of the most recent history entries the idle browser home surfaces under "Recent". */ private const val RECENTS_LIMIT = 12 @@ -150,13 +150,14 @@ private fun BrowserLauncher( accountViewModel: AccountViewModel, nav: INav, ) { - val context = LocalContext.current + val appServices = LocalAppServices.current + val appLauncher = rememberAppLauncher() val appStillLoadingStr = stringRes(Res.string.favorite_app_still_loading) - val apps by Amethyst.instance.favoriteApps.favorites + val apps by appServices.favoriteApps.favorites .collectAsStateWithLifecycle() - val history by Amethyst.instance.browserHistory.history + val history by appServices.browserHistory.history .collectAsStateWithLifecycle() - val iconKeys by Amethyst.instance.browserIcons.keys + val iconKeys by appServices.browserIcons.keys .collectAsStateWithLifecycle() // Fetch favorited nsite/napplet manifests up front so tapping one launches immediately instead of @@ -227,7 +228,7 @@ private fun BrowserLauncher( fun open(text: String) { val target = OmniboxInput.resolve(text) ?: return - FavoriteAppLauncher.launchUrl(context, target.url, target.forceTor) + appLauncher.launchUrl(target.url, target.forceTor) } // Pin/unpin a plain web URL by its favorite id. Shared by the suggestion list and the Recent rows. @@ -236,10 +237,10 @@ private fun BrowserLauncher( label: String, ) { val id = "url:$url" - if (Amethyst.instance.favoriteApps.isFavorite(id)) { - Amethyst.instance.favoriteApps.remove(id) + if (appServices.favoriteApps.isFavorite(id)) { + appServices.favoriteApps.remove(id) } else { - Amethyst.instance.favoriteApps.add(FavoriteApp.WebApp(url, label.ifBlank { OmniboxInput.hostOf(url) ?: url }, System.currentTimeMillis())) + appServices.favoriteApps.add(FavoriteApp.WebApp(url, label.ifBlank { OmniboxInput.hostOf(url) ?: url }, System.currentTimeMillis())) } } @@ -291,7 +292,7 @@ private fun BrowserLauncher( historyUrls = historyUrls, onOpen = { open(it.url) }, onToggleFavorite = { toggleFavorite(it.url, it.label) }, - onRemoveFromHistory = { Amethyst.instance.browserHistory.remove(it) }, + onRemoveFromHistory = { appServices.browserHistory.remove(it) }, modifier = contentModifier, ) else -> { @@ -306,12 +307,12 @@ private fun BrowserLauncher( suggested = suggested, nsites = followedNsites, napplets = followedNapplets, - onOpenApp = { FavoriteAppLauncher.launch(context, it, appStillLoadingStr) }, - onRemoveApp = { Amethyst.instance.favoriteApps.remove(it.id) }, - onAddApp = { Amethyst.instance.favoriteApps.add(it) }, + onOpenApp = { appLauncher.launch(it, appStillLoadingStr) }, + onRemoveApp = { appServices.favoriteApps.remove(it.id) }, + onAddApp = { appServices.favoriteApps.add(it) }, onOpenUrl = { open(it) }, onToggleRecentFavorite = { entry -> toggleFavorite(entry.url, entry.title.ifBlank { entry.host }) }, - onRemoveRecent = { Amethyst.instance.browserHistory.remove(it) }, + onRemoveRecent = { appServices.browserHistory.remove(it) }, modifier = contentModifier, ) } @@ -348,7 +349,7 @@ private fun OmniBar( onValueChange = onValueChange, modifier = Modifier.weight(1f), singleLine = true, - placeholder = { Text(stringRes(CommonsR.string.browser_address_hint)) }, + placeholder = { Text(stringRes(Res.string.browser_address_hint)) }, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.None, @@ -572,7 +573,8 @@ private fun SuggestedRow( onClick: () -> Unit, onAddFavorite: () -> Unit, ) { - val iconModel = remember(entry, iconKeys) { OmniboxInput.hostOf(entry.app.url)?.let(Amethyst.instance.browserIcons::iconModelFor) } + val appServices = LocalAppServices.current + val iconModel = remember(entry, iconKeys) { OmniboxInput.hostOf(entry.app.url)?.let(appServices.browserIcons::iconModelFor) } Row( modifier = Modifier @@ -644,7 +646,7 @@ private fun List.toDiscoverApps( /** Resolve a cached nsite/napplet manifest note into a launchable favorite + its description, or null. */ private fun Note.toDiscoverNostrApp(): DiscoverNostrApp? { val event = event ?: return null - val coordinate = FavoriteAppLauncher.coordinateOf(event) + val coordinate = favoriteCoordinateOf(event) val label: String val description: String? when (event) { @@ -803,7 +805,8 @@ private fun SiteIcon( iconKeys: Set, modifier: Modifier = Modifier, ) { - val model = remember(host, iconKeys) { Amethyst.instance.browserIcons.iconModelFor(host) } + val appServices = LocalAppServices.current + val model = remember(host, iconKeys) { appServices.browserIcons.iconModelFor(host) } val symbol = if (isFavorite) MaterialSymbols.Star else MaterialSymbols.Public val tint = MaterialTheme.colorScheme.onSurfaceVariant if (model == null) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt index 2d77460d81..2eb9f1f022 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt @@ -47,7 +47,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -69,9 +68,10 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_invite_title import com.vitorpamplona.amethyst.commons.resources.buzz_invite_workspace 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.AppLauncher +import com.vitorpamplona.amethyst.commons.ui.platform.rememberAppLauncher import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.quartz.buzz.invite.BuzzInviteLink import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.utils.TimeUtils @@ -85,7 +85,7 @@ import kotlinx.coroutines.launch * a flow the Buzz web app already implements and drives through `window.nostr`. So rather than * re-implement the legally-sensitive age/privacy consent natively, this confirms the workspace, * marks its relay as a Buzz dialect, and hands the URL to the in-app `window.nostr` browser - * ([FavoriteAppLauncher.launchUrl] → the sandboxed WebView), where the SPA signs the claim with + * ([AppLauncher.launchUrl] → the sandboxed WebView), where the SPA signs the claim with * the user's key. The user returns here (or to the app) once enrolled. */ @Composable @@ -95,7 +95,7 @@ fun BuzzInviteScreen( nav: INav, ) { val invite = remember(link) { BuzzInviteLink.parse(link) } - val context = LocalContext.current + val appLauncher = rememberAppLauncher() val scope = rememberCoroutineScope() var launched by remember { mutableStateOf(false) } @@ -201,7 +201,7 @@ fun BuzzInviteScreen( accountViewModel.account.buzzWorkspaces.join(relay) scope.launch { accountViewModel.account.relayAuthLedger.setDecision(relay.url, RelayAuthDecision.ALLOW) } } - FavoriteAppLauncher.launchUrl(context, link) + appLauncher.launchUrl(link) launched = true }, enabled = !expired, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/RelayStatusBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/RelayStatusBar.kt index 53371635cb..aa0db1c0a0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/RelayStatusBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/RelayStatusBar.kt @@ -55,7 +55,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -88,7 +87,7 @@ fun RelayStatusBar( .connectedRelaysFlow() .collectAsStateWithLifecycle() val authState = - Amethyst.instance.authCoordinator.receiver.authStateFlow + accountViewModel.host.relayAuthState .collectAsStateWithLifecycle() val buzzState = BuzzRelayDialect.flow.collectAsStateWithLifecycle() val info by loadRelayInfo(relay) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/detail/CalendarEventDetailScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/detail/CalendarEventDetailScreen.kt index bce0ec65b3..9915e54662 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/detail/CalendarEventDetailScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/detail/CalendarEventDetailScreen.kt @@ -96,13 +96,13 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.ui.note.types.CalendarRsvpRow import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size30dp import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.addToPhoneCalendar import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.formatCalendarRange diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt index fae0dec2d3..b51e0f970c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt @@ -132,12 +132,12 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowU import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.feed.types.observeUserNameByHex +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CoordinatorIdentityRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightChat import com.vitorpamplona.amethyst.commons.util.toShortDisplay import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SectionCollapse import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SectionExpand import com.vitorpamplona.quartz.cordn.spec00Coordinator.JoinRequest diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt index b1da5855ba..5b1590c891 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatScreen.kt @@ -73,9 +73,9 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SearchTopBarAction import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.LocalChatActingIdentities import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.LocalChatDisplayNameResolver import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.LocalChatReactOverride diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt index 3e53546877..e6652bca50 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt @@ -69,10 +69,10 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.UserDrawerSearchTopBar +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.quartz.experimental.bitchat.geohash.GeohashChannelLevel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/NewGeohashChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/NewGeohashChatScreen.kt index eb54b6aac2..7e8c436be2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/NewGeohashChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/NewGeohashChatScreen.kt @@ -67,8 +67,8 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.quartz.experimental.bitchat.geohash.GeohashChannelLevel import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt index fcb92a4a6a..e76d675bbf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt @@ -110,9 +110,9 @@ import com.vitorpamplona.amethyst.commons.ui.note.timeAgo import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelPreviewLoader import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChannelId import com.vitorpamplona.quartz.concord.cord04Roles.ConcordPermissions import com.vitorpamplona.quartz.nip01Core.core.HexKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt index 01c517f2b3..64895fab11 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt @@ -77,6 +77,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ShorterTopAppBar import com.vitorpamplona.amethyst.commons.ui.note.timeAgo import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.platform.rememberConcordImageModel import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt index d715b3089e..970e331ae9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt @@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.RenderRelayIcon +import com.vitorpamplona.amethyst.commons.ui.platform.rememberConcordImageModel import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.MediumRelayIconModifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/header/LiveStreamGoalHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/header/LiveStreamGoalHeader.kt index 615928c14a..ae08a351ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/header/LiveStreamGoalHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip53LiveActivities/header/LiveStreamGoalHeader.kt @@ -41,13 +41,13 @@ import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel import com.vitorpamplona.amethyst.commons.ui.components.LoadNote import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction import com.vitorpamplona.amethyst.commons.ui.note.types.GoalProgressBar import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size20dp import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.quartz.nip75ZapGoals.ZapGoalEvent /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt index 55720bc405..aff716d859 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt @@ -108,13 +108,13 @@ import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.platform.GeohashLocationPickerDialog import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.LoadRelayGroupChannel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupCardWarmupSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt index bf4d44624d..90e207fcef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt @@ -132,6 +132,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.NonClickableUserPictures import com.vitorpamplona.amethyst.commons.ui.note.ObserveDraftEvent import com.vitorpamplona.amethyst.commons.ui.note.elements.TimeAgoStyle import com.vitorpamplona.amethyst.commons.ui.note.elements.ToggleableTimeAgoText +import com.vitorpamplona.amethyst.commons.ui.platform.rememberConcordImageModel import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.feed.types.observeUserNameByHex import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.privateDM.header.RoomNameDisplay @@ -154,7 +155,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.marmotOth import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.rememberMarmotGroupAvatarUrl import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.reportWarningContentDescription import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordLeaveDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.LoadEphemeralChatChannel import com.vitorpamplona.quartz.buzz.notifications.MemberAddedNotificationEvent import com.vitorpamplona.quartz.cordn.appEncryptedMedia.CordnMediaTag diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt index c4314574a4..c4e82b4bc5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt @@ -59,6 +59,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.DisplayAuthorBanner import com.vitorpamplona.amethyst.commons.ui.note.Gallery import com.vitorpamplona.amethyst.commons.ui.note.LoadPublicChatChannel +import com.vitorpamplona.amethyst.commons.ui.note.platform.LikeReaction +import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.HalfTopPadding import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder @@ -67,8 +69,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.util.equalImmutableLists import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.LikeReaction -import com.vitorpamplona.amethyst.ui.note.ZapReaction import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt index bfc457075a..6bca972c92 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt @@ -60,6 +60,8 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.LeftPictureLayout import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.DisplayAuthorBanner import com.vitorpamplona.amethyst.commons.ui.note.Gallery +import com.vitorpamplona.amethyst.commons.ui.note.platform.LikeReaction +import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.HalfTopPadding import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder @@ -68,8 +70,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.util.equalImmutableLists import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.LikeReaction -import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt index 14ff0c3b40..421ee5f46b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt @@ -46,6 +46,8 @@ import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage import com.vitorpamplona.amethyst.commons.ui.layouts.LeftPictureLayout import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.commons.ui.note.platform.LikeReaction +import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction import com.vitorpamplona.amethyst.commons.ui.theme.HalfTopPadding import com.vitorpamplona.amethyst.commons.ui.theme.RowColSpacing5dp import com.vitorpamplona.amethyst.commons.ui.theme.SimpleImageBorder @@ -54,8 +56,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.ui.theme.nip05 import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.LikeReaction -import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.FavoriteAlgoFeedToggle import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.observeAppDefinition diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt index cd518375cc..d39c806415 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt @@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent import com.vitorpamplona.quartz.nip99Classifieds.tags.PriceTag +import com.vitorpamplona.quartz.utils.parseBigDecimalOrNull @Immutable data class ClassifiedsThumb( @@ -141,7 +142,7 @@ fun InnerRenderClassifiedsThumb( card.price?.let { val priceTag = remember(card) { - val newAmount = it.amount.toBigDecimalOrNull()?.let { showAmountInteger(it) } ?: it.amount + val newAmount = parseBigDecimalOrNull(it.amount)?.let { showAmountInteger(it) } ?: it.amount if (it.frequency != null && it.currency != null) { "$newAmount ${it.currency}/${it.frequency}" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt index a4c51199df..374957bf64 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt @@ -54,12 +54,10 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.platform.LocalContext 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 com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.browser.OmniboxInput import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon @@ -73,11 +71,12 @@ import com.vitorpamplona.amethyst.commons.resources.favorite_apps import com.vitorpamplona.amethyst.commons.resources.favorite_apps_empty import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices +import com.vitorpamplona.amethyst.commons.ui.platform.rememberAppLauncher +import com.vitorpamplona.amethyst.commons.ui.platform.rememberNappletIconModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.amethyst.favorites.PreloadFavoriteNostrApps -import com.vitorpamplona.amethyst.favorites.rememberNappletIconModel /** * The Favorite Apps grid: the user's pinned web clients / nsites / napplets as big launch buttons. @@ -91,9 +90,10 @@ fun FavoriteAppsScreen( accountViewModel: AccountViewModel, nav: INav, ) { + val appServices = LocalAppServices.current val appStillLoadingStr = stringRes(Res.string.favorite_app_still_loading) - val context = LocalContext.current - val apps by Amethyst.instance.favoriteApps.favorites + val appLauncher = rememberAppLauncher() + val apps by appServices.favoriteApps.favorites .collectAsStateWithLifecycle() // Fetch favorited nsite/napplet manifests up front so a tap launches immediately instead of showing @@ -125,8 +125,8 @@ fun FavoriteAppsScreen( } else { FavoriteAppsGrid( apps = apps, - onOpen = { FavoriteAppLauncher.launch(context, it, appStillLoadingStr) }, - onRemove = { Amethyst.instance.favoriteApps.remove(it.id) }, + onOpen = { appLauncher.launch(it, appStillLoadingStr) }, + onRemove = { appServices.favoriteApps.remove(it.id) }, modifier = Modifier .fillMaxSize() @@ -184,18 +184,19 @@ internal fun FavoriteAppCell( onOpen: () -> Unit, onRemove: () -> Unit, ) { + val appServices = LocalAppServices.current var menuOpen by remember { mutableStateOf(false) } // For a plain web favorite, prefer the favicon captured when its site was opened; an nsite/napplet uses // the verified icon blob bundled in its own content. Observing the key set recomputes the model as a // captured favicon arrives. - val iconKeys by Amethyst.instance.browserIcons.keys + val iconKeys by appServices.browserIcons.keys .collectAsStateWithLifecycle() val faviconModel = when (app) { is FavoriteApp.WebApp -> remember(app, iconKeys) { - OmniboxInput.hostOf(app.url)?.let(Amethyst.instance.browserIcons::iconModelFor) + OmniboxInput.hostOf(app.url)?.let(appServices.browserIcons::iconModelFor) } is FavoriteApp.NostrApp -> rememberNappletIconModel(app.coordinate) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt index bc55ce3516..7329448b51 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt @@ -69,6 +69,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ShorterTopAppBar import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TitleIconModifier import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarSize import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote +import com.vitorpamplona.amethyst.commons.ui.note.platform.LikeReaction +import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.HalfHorzSpacer @@ -76,9 +78,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier import com.vitorpamplona.amethyst.commons.ui.theme.SpacedBy2dp import com.vitorpamplona.amethyst.commons.ui.theme.TabRowHeight import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.LikeReaction import com.vitorpamplona.amethyst.ui.note.ReplyReaction -import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.screen.UserFeedView import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.dal.FollowPackFeedConversationsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.dal.FollowPackFeedNewThreadFeedViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt index 45f447610e..6e244bc1e9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt @@ -106,10 +106,10 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.geocacheEmoji import com.vitorpamplona.amethyst.commons.ui.note.geocacheLabelRes import com.vitorpamplona.amethyst.commons.ui.platform.GeohashLocationPickerDialog +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.quartz.nip01Core.tags.geohash.toGeoHash import com.vitorpamplona.quartz.nipCCGeocaching.listing.GeocacheGeohash import com.vitorpamplona.quartz.nipCCGeocaching.listing.tags.CacheSize diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/detail/GeocacheOwnerActions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/detail/GeocacheOwnerActions.kt index 11d10a89a2..f69a4d5c89 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/detail/GeocacheOwnerActions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/detail/GeocacheOwnerActions.kt @@ -60,9 +60,9 @@ import com.vitorpamplona.amethyst.commons.resources.geocache_owner_no_finders import com.vitorpamplona.amethyst.commons.resources.geocache_owner_qr_warning import com.vitorpamplona.amethyst.commons.resources.geocache_owner_show_qr import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nipCCGeocaching.firstToFind.FirstToFindResolver import com.vitorpamplona.quartz.nipCCGeocaching.listing.GeocacheListingEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt index 1c42247bbd..b9f214640e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt @@ -45,10 +45,10 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarP import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SearchTopBarAction import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.dal.GeoHashFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.datasource.GeoHashFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryHomeUi.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryHomeUi.kt index fdb89b6ed8..7952419dc3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryHomeUi.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryHomeUi.kt @@ -70,11 +70,11 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.commons.ui.note.elements.TimeAgo +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip34Git.git.GitCommit import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderGeohashBubble.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderGeohashBubble.kt index 1d3aa3d9fa..430fa01877 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderGeohashBubble.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderGeohashBubble.kt @@ -34,9 +34,9 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.geohashChat.GeohashChatChannel import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt index 804ce0e0ef..eae4d35095 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt @@ -136,6 +136,7 @@ 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.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -143,7 +144,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.ui.note.authenticate import com.vitorpamplona.amethyst.ui.note.rememberAuthPromptLabels import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.quartz.nip19Bech32.toNsec import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49 import kotlinx.coroutines.CoroutineScope 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 6a6b38d0c7..1763fa96a3 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 @@ -114,9 +114,9 @@ import com.vitorpamplona.amethyst.commons.resources.nip46_signer_reconnecting import com.vitorpamplona.amethyst.commons.resources.nip46_signer_remote_app 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.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.rememberManifestIconModel -import com.vitorpamplona.amethyst.favorites.rememberWebAppIconModel import com.vitorpamplona.amethyst.napplet.NappletBrokerService import com.vitorpamplona.amethyst.napplet.WebSitePermissionRegistry import com.vitorpamplona.amethyst.napplet.counterpartyLabel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt index 25c7625df0..c7f9661616 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt @@ -52,7 +52,6 @@ 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 com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46PermissionAuthorizer import com.vitorpamplona.amethyst.commons.connectedApps.signers.AppSignerPolicy import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionLedger @@ -69,13 +68,15 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_permissions_title import com.vitorpamplona.amethyst.commons.resources.napplet_policy_full_trust import com.vitorpamplona.amethyst.commons.resources.napplet_policy_paranoid import com.vitorpamplona.amethyst.commons.resources.napplet_policy_reasonable +import com.vitorpamplona.amethyst.commons.resources.napplet_untitled 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.platform.rememberManifestIconModel +import com.vitorpamplona.amethyst.commons.ui.platform.rememberWebAppIconModel +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.rememberManifestIconModel -import com.vitorpamplona.amethyst.favorites.rememberWebAppIconModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.datasource.ConnectedAppsFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip19Bech32.entities.NPub @@ -86,7 +87,6 @@ import com.vitorpamplona.quartz.nip5dNapplets.NappletManifest import com.vitorpamplona.quartz.nip5dNapplets.RootNappletEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import com.vitorpamplona.amethyst.commons.R as CommonsR /** Author placeholder used by the browser permission path — not a real pubkey. */ private const val BROWSER_AUTHOR = "browser" @@ -101,8 +101,9 @@ fun ConnectedAppsScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val capabilityLedger = Amethyst.instance.nappletPermissionLedger - val signerLedger = remember { NostrSignerPermissionLedger(Amethyst.instance.signerPermissionStore) } + val appServices = LocalAppServices.current + val capabilityLedger = appServices.nappletPermissionLedger + val signerLedger = remember { NostrSignerPermissionLedger(appServices.signerPermissionStore) } var items by remember { mutableStateOf?>(null) } var nappletAuthors by remember { mutableStateOf>(emptySet()) } @@ -156,7 +157,7 @@ fun ConnectedAppsScreen( } else -> { - val untitled = stringRes(CommonsR.string.napplet_untitled) + val untitled = stringRes(Res.string.napplet_untitled) LazyColumn( modifier = Modifier.fillMaxSize().padding(padding), contentPadding = PaddingValues(16.dp), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt index 86d189d6b6..b85b8f9380 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt @@ -91,6 +91,8 @@ import com.vitorpamplona.amethyst.commons.ui.note.LongPressToQuickAction import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.commons.ui.note.elements.MoreOptionsButton +import com.vitorpamplona.amethyst.commons.ui.note.platform.LikeReaction +import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction import com.vitorpamplona.amethyst.commons.ui.note.timeAgoNoDot import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -105,8 +107,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.LikeReaction -import com.vitorpamplona.amethyst.ui.note.ZapReaction import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.LoadParticipants import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity import com.vitorpamplona.quartz.nip01Core.core.Address diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt index ccd21a2374..cd3a8ed3d5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/PictureCardCompose.kt @@ -49,9 +49,9 @@ import com.vitorpamplona.amethyst.commons.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.commons.ui.components.collectContentWarningReasons import com.vitorpamplona.amethyst.commons.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.components.ZoomableContentView -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.UserCardHeader import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW import com.vitorpamplona.quartz.nip68Picture.PictureEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastEpisodeListItem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastEpisodeListItem.kt index 3d74d3d04d..f6a1dd63f6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastEpisodeListItem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastEpisodeListItem.kt @@ -44,13 +44,13 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_season import com.vitorpamplona.amethyst.commons.resources.podcast_season_episode import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeAgoLabels import com.vitorpamplona.amethyst.commons.ui.note.timeAgoWith import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.note.types.PodcastEpisodeAudioPlayer import com.vitorpamplona.quartz.podcasts.PodcastEpisode diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastHeader.kt index 1d92d618ce..16432a19d7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/PodcastHeader.kt @@ -44,6 +44,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.podcast_episode_count import com.vitorpamplona.amethyst.commons.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.ui.note.types.PodcastCoverCard import com.vitorpamplona.amethyst.commons.ui.note.types.PodcastPeople import com.vitorpamplona.amethyst.commons.ui.pluralStringRes @@ -51,7 +52,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.quartz.nipF4Podcasts.metadata.PodcastMetadataEvent import com.vitorpamplona.quartz.podcasts.PodcastShow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt index 47aefaed52..4007521128 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt @@ -77,11 +77,11 @@ import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDial import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.CoverImagePicker import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadInProgressBanner -import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.collections.immutable.persistentListOf import org.jetbrains.compose.resources.StringResource diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt index 5ecce348ac..fde68ca8f8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt @@ -54,7 +54,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -103,6 +102,8 @@ 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.LocalAppServices +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SegmentedChoiceTile @@ -111,7 +112,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsControlRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsDivider import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSwitchTile -import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import org.jetbrains.compose.resources.StringResource @@ -119,7 +119,7 @@ import org.jetbrains.compose.resources.StringResource @OptIn(ExperimentalMaterial3Api::class) @Composable fun PrivacyOptionsScreen(nav: INav) { - PrivacyOptionsScreen(Amethyst.instance.torPrefs.value, nav) + PrivacyOptionsScreen(LocalAppServices.current.torSettings, nav) } @OptIn(ExperimentalMaterial3Api::class) 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 d878498609..bf49de7a0a 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,9 +66,9 @@ 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.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.quartz.nipA3PaymentTargets.PaymentTarget import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt index f935fd2575..9ae17da7eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt @@ -24,9 +24,9 @@ import androidx.compose.runtime.Composable import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.qrcode.ScannedPayload import com.vitorpamplona.amethyst.commons.qrcode.classifyScannedPayload +import com.vitorpamplona.amethyst.commons.ui.platform.QrCodeScannerDialog +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ScanOutcome import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.QrCodeScannerDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.ScanOutcome import com.vitorpamplona.amethyst.ui.uriToRoute import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CancellationException diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt index cfda159a4b..8a31f94ec1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt @@ -64,6 +64,8 @@ import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.ObserveAndDisplayNIP05 import com.vitorpamplona.amethyst.commons.ui.richtext.CreateTextWithEmoji import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.KeepScreenBrightAndAwake +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Font14SP import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt index e05e6246cd..d1dbf238eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt @@ -104,6 +104,7 @@ import com.vitorpamplona.amethyst.commons.resources.qr_scanner_grant_camera import com.vitorpamplona.amethyst.commons.resources.qr_scanner_no_code_in_image import com.vitorpamplona.amethyst.commons.resources.qr_scanner_open_settings import com.vitorpamplona.amethyst.commons.resources.qr_scanner_unavailable +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ScanOutcome import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.ui.call.openAppSettings import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge @@ -114,15 +115,6 @@ import kotlinx.coroutines.launch import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -/** What the caller did with a decoded payload — and therefore what the scanner does next. */ -enum class ScanOutcome { - /** The caller acted on it. Close the scanner. */ - Handled, - - /** Decoded fine, but this screen has nothing to do with it. Explain, and keep scanning. */ - NotSupported, -} - /** * Full-screen QR scanner. * diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt index b5fb025c3f..5e42cbd9f6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt @@ -21,8 +21,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.compose.runtime.Immutable -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.User +import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStat import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -41,6 +41,6 @@ fun relaySetupInfoBuilder( ): BasicRelaySetupInfo = BasicRelaySetupInfo( relay = normalized, - relayStat = Amethyst.instance.relayStats.get(normalized), + relayStat = LocalCache.appHost.relayStats?.get(normalized) ?: RelayStat(), forcesTor = forcesTor, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt index 7d565eef51..b39de5b486 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.relays.ui.CountFilter @@ -131,7 +130,7 @@ abstract class BasicRelaySetupInfoModel : ViewModel() { relaySetupInfoBuilder( normalized = it, forcesTor = - Amethyst.instance.torEvaluatorFlow.flow.value + accountViewModel.host.torRelayEvaluation.value .useTor(it), ) }.distinctBy { it.relay } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt index 8e7d24af54..60273e00e4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.connected import androidx.compose.runtime.Stable -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel @@ -36,7 +35,7 @@ class ConnectedRelayListViewModel : BasicRelaySetupInfoModel() { return relayList .map { - val reqs = Amethyst.instance.client.activeRequests(it) + val reqs = account.client.activeRequests(it) val users = mutableSetOf() reqs.forEach { _, filters -> @@ -55,9 +54,9 @@ class ConnectedRelayListViewModel : BasicRelaySetupInfoModel() { BasicRelaySetupInfo( relay = it, - relayStat = Amethyst.instance.relayStats.get(it), + relayStat = accountViewModel.host.relayStats.get(it), forcesTor = - Amethyst.instance.torEvaluatorFlow.flow.value + accountViewModel.host.torRelayEvaluation.value .useTor(it), users = users.mapNotNull { hex -> LocalCache.checkGetOrCreateUser(hex) }, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt index ef75453d74..1b7c903bd3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt @@ -92,18 +92,18 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_group_fallback_name import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage 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.platform.LoadCityName +import com.vitorpamplona.amethyst.commons.ui.platform.rememberConcordImageModel +import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.RelayIconFilter import com.vitorpamplona.amethyst.commons.ui.theme.allGoodColor import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.ui.theme.warningColor import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.creators.location.LoadCityName -import com.vitorpamplona.amethyst.ui.pluralStringRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.loadMarmotRelayIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.rememberMarmotGroupAvatarUrl -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel -import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index ec9e073d0d..e83fea8d75 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -71,10 +71,10 @@ import com.vitorpamplona.amethyst.commons.ui.settings.SettingsCategory import com.vitorpamplona.amethyst.commons.ui.settings.SettingsEntry import com.vitorpamplona.amethyst.commons.ui.settings.SettingsIcon import com.vitorpamplona.amethyst.commons.ui.settings.filterSettings +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.jetbrains.compose.resources.StringResource diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt index f7ff0a81e3..139504d5d2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt @@ -30,22 +30,17 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.verticalScroll import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SegmentedButton -import androidx.compose.material3.SegmentedButtonDefaults -import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -61,7 +56,6 @@ 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.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow @@ -122,7 +116,6 @@ import com.vitorpamplona.amethyst.commons.resources.ui_feature_set_type_simplifi import com.vitorpamplona.amethyst.commons.resources.ui_style import com.vitorpamplona.amethyst.commons.resources.ui_style_description import com.vitorpamplona.amethyst.commons.ui.components.SpinnerSelectionDialog -import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner 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 @@ -134,7 +127,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.isLight import com.vitorpamplona.amethyst.commons.ui.theme.previewColor import com.vitorpamplona.amethyst.commons.ui.theme.toFontFamily import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap @@ -219,53 +211,6 @@ fun SettingsScreen( } } -/** - * A [SettingsBlockTile] whose control is a full-width [SingleChoiceSegmentedButtonRow]. - * This is the in-screen replacement for the old dropdown ([TextSpinner]) rows: every - * option is visible and one tap away. Best for 2–4 mutually-exclusive options. - * - * [optionTextStyle] lets each option render its own label preview — e.g. the font tiles - * draw each label in the very typeface / size it selects, so the row demonstrates the - * choices instead of only naming them. - */ -@Composable -internal fun SegmentedChoiceTile( - icon: MaterialSymbol, - title: StringResource, - description: StringResource, - options: List, - labelRes: (T) -> StringResource, - selected: T, - onSelect: (T) -> Unit, - optionTextStyle: (@Composable (T) -> TextStyle)? = null, -) { - SettingsBlockTile( - icon = icon, - title = stringRes(title), - description = stringRes(description), - ) { - SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) { - options.forEachIndexed { index, option -> - SegmentedButton( - selected = option == selected, - onClick = { onSelect(option) }, - shape = SegmentedButtonDefaults.itemShape(index = index, count = options.size), - // Drop the default check icon: the fill already signals selection, and the icon - // steals ~24dp that the label needs in a 3–4-up row. - icon = {}, - ) { - Text( - text = stringRes(labelRes(option)), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = optionTextStyle?.invoke(option) ?: LocalTextStyle.current, - ) - } - } - } - } -} - /** A [SettingsControlRow] with a [Switch] backed by a [BooleanType] flow (ALWAYS ⇔ on). */ @Composable private fun BooleanSwitchTile( @@ -617,91 +562,3 @@ private fun AccentColorSwatch( } } } - -@Composable -fun SettingsRow( - name: StringResource, - description: StringResource, - selectedItems: ImmutableList, - selectedIndex: Int, - onSelect: (Int) -> Unit, -) { - SettingsRow(name, description) { - TextSpinner( - label = "", - placeholder = selectedItems[selectedIndex].title, - options = selectedItems, - onSelect = onSelect, - modifier = Modifier.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), - ) - } -} - -@Composable -fun SettingsRow( - name: StringResource, - description: StringResource, - modifier: Modifier = Modifier.fillMaxWidth(), - content: @Composable () -> Unit, -) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = modifier, - horizontalArrangement = Arrangement.spacedBy(5.dp), - ) { - Column( - modifier = Modifier.weight(2.0f), - verticalArrangement = Arrangement.spacedBy(3.dp), - ) { - Text( - text = stringRes(name), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = stringRes(description), - style = MaterialTheme.typography.bodySmall, - color = Color.Gray, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - ) - } - - Column( - Modifier.weight(1f), - horizontalAlignment = Alignment.End, - ) { - content() - } - } -} - -@Composable -fun SettingsRow( - name: StringResource, - description: StringResource, -) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(5.dp), - ) { - Column( - modifier = Modifier.weight(2.0f), - verticalArrangement = Arrangement.spacedBy(3.dp), - ) { - Text( - text = stringRes(name), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = stringRes(description), - style = MaterialTheme.typography.bodySmall, - color = Color.Gray, - maxLines = 3, - overflow = TextOverflow.Ellipsis, - ) - } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt index 8b09e9d267..b4ca1a28cc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt @@ -67,7 +67,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon import com.vitorpamplona.amethyst.commons.icons.symbols.Icon @@ -91,6 +90,8 @@ import com.vitorpamplona.amethyst.commons.resources.bottom_bar_settings_reorder_ 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.LocalAppServices +import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size22Modifier import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -102,7 +103,6 @@ import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCategory import com.vitorpamplona.amethyst.ui.navigation.bottombars.rememberFavoriteIconModel import com.vitorpamplona.amethyst.ui.navigation.bottombars.rememberGroupEntryDisplay -import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEntry import com.vitorpamplona.quartz.nip51Lists.simpleGroupList.GroupTag import kotlinx.coroutines.flow.MutableStateFlow @@ -498,7 +498,7 @@ private fun PickerChildren( ) { when (item) { NavBarItem.BROWSER -> { - val favorites by Amethyst.instance.favoriteApps.favorites + val favorites by LocalAppServices.current.favoriteApps.favorites .collectAsStateWithLifecycle() if (favorites.isEmpty()) { EmptyChildHint(Res.string.bottom_bar_settings_no_favorites) @@ -818,7 +818,7 @@ private fun rememberPinnedVisual( PinnedVisual.Glyph(def?.icon ?: MaterialSymbols.Apps, def?.let { stringRes(it.labelRes) } ?: "") } is BottomBarEntry.Favorite -> { - val favorites by Amethyst.instance.favoriteApps.favorites + val favorites by LocalAppServices.current.favoriteApps.favorites .collectAsStateWithLifecycle() val app = favorites.firstOrNull { it.id == entry.favoriteId } if (app != null) PinnedVisual.Favorite(app) else PinnedVisual.Glyph(MaterialSymbols.Public, "") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsRows.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsRows.kt new file mode 100644 index 0000000000..997034fd9d --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsRows.kt @@ -0,0 +1,183 @@ +/* + * 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.screen.loggedIn.settings + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SegmentedButton +import androidx.compose.material3.SegmentedButtonDefaults +import androidx.compose.material3.SingleChoiceSegmentedButtonRow +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol +import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner +import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer +import com.vitorpamplona.amethyst.commons.ui.stringRes +import kotlinx.collections.immutable.ImmutableList +import org.jetbrains.compose.resources.StringResource + +/** + * A [SettingsBlockTile] whose control is a full-width [SingleChoiceSegmentedButtonRow]. + * This is the in-screen replacement for the old dropdown ([TextSpinner]) rows: every + * option is visible and one tap away. Best for 2–4 mutually-exclusive options. + * + * [optionTextStyle] lets each option render its own label preview — e.g. the font tiles + * draw each label in the very typeface / size it selects, so the row demonstrates the + * choices instead of only naming them. + */ +@Composable +fun SegmentedChoiceTile( + icon: MaterialSymbol, + title: StringResource, + description: StringResource, + options: List, + labelRes: (T) -> StringResource, + selected: T, + onSelect: (T) -> Unit, + optionTextStyle: (@Composable (T) -> TextStyle)? = null, +) { + SettingsBlockTile( + icon = icon, + title = stringRes(title), + description = stringRes(description), + ) { + SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) { + options.forEachIndexed { index, option -> + SegmentedButton( + selected = option == selected, + onClick = { onSelect(option) }, + shape = SegmentedButtonDefaults.itemShape(index = index, count = options.size), + // Drop the default check icon: the fill already signals selection, and the icon + // steals ~24dp that the label needs in a 3–4-up row. + icon = {}, + ) { + Text( + text = stringRes(labelRes(option)), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = optionTextStyle?.invoke(option) ?: LocalTextStyle.current, + ) + } + } + } + } +} + +@Composable +fun SettingsRow( + name: StringResource, + description: StringResource, + selectedItems: ImmutableList, + selectedIndex: Int, + onSelect: (Int) -> Unit, +) { + SettingsRow(name, description) { + TextSpinner( + label = "", + placeholder = selectedItems[selectedIndex].title, + options = selectedItems, + onSelect = onSelect, + modifier = Modifier.windowInsetsPadding(WindowInsets(0.dp, 0.dp, 0.dp, 0.dp)), + ) + } +} + +@Composable +fun SettingsRow( + name: StringResource, + description: StringResource, + modifier: Modifier = Modifier.fillMaxWidth(), + content: @Composable () -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(5.dp), + ) { + Column( + modifier = Modifier.weight(2.0f), + verticalArrangement = Arrangement.spacedBy(3.dp), + ) { + Text( + text = stringRes(name), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringRes(description), + style = MaterialTheme.typography.bodySmall, + color = Color.Gray, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + ) + } + + Column( + Modifier.weight(1f), + horizontalAlignment = Alignment.End, + ) { + content() + } + } +} + +@Composable +fun SettingsRow( + name: StringResource, + description: StringResource, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(5.dp), + ) { + Column( + modifier = Modifier.weight(2.0f), + verticalArrangement = Arrangement.spacedBy(3.dp), + ) { + Text( + text = stringRes(name), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = stringRes(description), + style = MaterialTheme.typography.bodySmall, + color = Color.Gray, + maxLines = 3, + overflow = TextOverflow.Ellipsis, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt index c2bf67e6fb..1d96ceee8d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt @@ -82,10 +82,10 @@ import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.model.cordn.AndroidCordnBlobStore -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.quartz.cordn.appMultiDevice.CordnHandoffCode diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt index 37040337e7..a54b850f24 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt @@ -125,9 +125,9 @@ 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.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt index 3dff6d975c..197918f1a6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/VideoCardCompose.kt @@ -51,9 +51,9 @@ import com.vitorpamplona.amethyst.commons.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.commons.ui.components.collectContentWarningReasons import com.vitorpamplona.amethyst.commons.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.components.ZoomableContentView -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.UserCardHeader import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip31Alts.alt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt index b4a2faa10d..8ea539a211 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt @@ -75,6 +75,7 @@ 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.LoadAddressableNote import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.ui.note.types.AppAuthorLine import com.vitorpamplona.amethyst.commons.ui.note.types.AppIcon import com.vitorpamplona.amethyst.commons.ui.note.types.AppLinksColumn @@ -99,7 +100,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder import com.vitorpamplona.amethyst.commons.ui.thread.drawReplyLevel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal.ThreadFeedViewModel import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent import com.vitorpamplona.quartz.nip01Core.core.Address diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index 9f839d5c35..3a463ac70e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -144,6 +144,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.elements.TimeAgo import com.vitorpamplona.amethyst.commons.ui.note.elements.TimeAgoStyle import com.vitorpamplona.amethyst.commons.ui.note.nip22Comments.DisplayCommentScope import com.vitorpamplona.amethyst.commons.ui.note.observeEdits +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.ui.note.types.BadgeDisplay import com.vitorpamplona.amethyst.commons.ui.note.types.DisplayBlockedRelayList import com.vitorpamplona.amethyst.commons.ui.note.types.DisplayBroadcastRelayList @@ -262,7 +263,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.thread.LevelFeedViewModel import com.vitorpamplona.amethyst.ui.components.ZoomableContentView -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.amethyst.ui.note.types.AudioHeader import com.vitorpamplona.amethyst.ui.note.types.AudioTrackHeader import com.vitorpamplona.amethyst.ui.note.types.RenderGitIssueEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileMetadataCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileMetadataCardCompose.kt index c88395ced3..aa5b9947f3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileMetadataCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/FileMetadataCardCompose.kt @@ -45,11 +45,11 @@ import com.vitorpamplona.amethyst.commons.ui.components.ContentWarningGate import com.vitorpamplona.amethyst.commons.ui.components.collectContentWarningReasons import com.vitorpamplona.amethyst.commons.ui.components.mediaSizingModifier import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReactionsRow import com.vitorpamplona.amethyst.commons.ui.note.types.FileMetadataAttachmentCard import com.vitorpamplona.amethyst.commons.ui.note.types.toMediaContent import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.components.ZoomableContentView -import com.vitorpamplona.amethyst.ui.note.ReactionsRow import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW import com.vitorpamplona.quartz.nip94FileMetadata.FileMetadataEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt index 216a41cca9..7408397c0a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt @@ -126,6 +126,7 @@ import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.sats import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.util.showAmount import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.components.namecoin.NamecoinResolutionRow @@ -144,7 +145,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.compose.resources.StringResource -import java.text.NumberFormat /** Shared with the profile Send Payment screen's on-chain rail. */ internal enum class FeeTier( @@ -706,7 +706,7 @@ private fun AmountSection( if (belowMinimum) { Spacer(Modifier.height(4.dp)) Text( - text = stringRes(Res.string.onchain_send_min_warning, NumberFormat.getNumberInstance().format(MIN_ONCHAIN_ZAP_SATS)), + text = stringRes(Res.string.onchain_send_min_warning, formatGrouped(MIN_ONCHAIN_ZAP_SATS)), style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.error, ) @@ -806,7 +806,7 @@ private fun SendButton( modifier = Modifier.size(18.dp), ) Spacer(Modifier.size(8.dp)) - val sats = if (amountSats != null && amountSats > 0) NumberFormat.getNumberInstance().format(amountSats) else null + val sats = if (amountSats != null && amountSats > 0) formatGrouped(amountSats) else null Text( text = when { @@ -866,7 +866,7 @@ private fun SplitsRecipientSection( if (share != null) { val belowDust = share.sats < OnchainZapBuilder.DUST_THRESHOLD_SATS Text( - text = "${NumberFormat.getNumberInstance().format(share.sats)} sats", + text = "${formatGrouped(share.sats)} sats", style = MaterialTheme.typography.bodySmall, color = if (belowDust) { @@ -971,12 +971,12 @@ private fun SuccessBody(result: OnchainZapSendResult.Success) { ResultRow(stringRes(Res.string.onchain_send_result_transaction), result.txid) ResultRow( stringRes(Res.string.onchain_send_result_fee), - stringRes(Res.string.onchain_send_sats_amount, NumberFormat.getNumberInstance().format(result.feeSats)), + stringRes(Res.string.onchain_send_sats_amount, formatGrouped(result.feeSats)), ) if (result.changeSats > 0) { ResultRow( stringRes(Res.string.onchain_send_result_change), - stringRes(Res.string.onchain_send_sats_amount, NumberFormat.getNumberInstance().format(result.changeSats)), + stringRes(Res.string.onchain_send_sats_amount, formatGrouped(result.changeSats)), ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt index 85f789f232..2b3a8a19f1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt @@ -74,9 +74,9 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_description import com.vitorpamplona.amethyst.commons.resources.wallet_receive import com.vitorpamplona.amethyst.commons.resources.wallet_sats import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import java.text.NumberFormat @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt index 317d0b09be..497f0ac69d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt @@ -78,6 +78,8 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import org.jetbrains.compose.resources.StringResource +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid sealed class SendState { data object Idle : SendState() @@ -338,6 +340,7 @@ class WalletViewModel : ViewModel() { } /** Adds a CLINK debit pointer (`ndebit1…`) as a spend-only payment source. */ + @OptIn(ExperimentalUuidApi::class) fun addClinkDebitWallet( name: String, ndebit: String, @@ -347,9 +350,7 @@ class WalletViewModel : ViewModel() { val entry = ClinkDebitWalletEntryNorm( id = - java.util.UUID - .randomUUID() - .toString(), + Uuid.random().toString(), name = name.ifBlank { "Debit" }, pointer = pointer, ) @@ -384,6 +385,7 @@ class WalletViewModel : ViewModel() { } } + @OptIn(ExperimentalUuidApi::class) fun addWallet( name: String, uri: Nip47WalletConnect.Nip47URINorm, @@ -392,9 +394,7 @@ class WalletViewModel : ViewModel() { val entry = NwcWalletEntryNorm( id = - java.util.UUID - .randomUUID() - .toString(), + Uuid.random().toString(), name = name.ifBlank { "Wallet" }, uri = uri, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt index 2d01d2adab..04a3631842 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Theme.kt @@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.LocalInlineQuoteRenderer import com.vitorpamplona.amethyst.commons.ui.components.LocalTranslationPlatform import com.vitorpamplona.amethyst.commons.ui.note.platform.LocalNotePlatform import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppPlatform +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices import com.vitorpamplona.amethyst.commons.ui.richtext.LocalRichTextPlatform import com.vitorpamplona.amethyst.commons.ui.screen.DisplaySettings import com.vitorpamplona.amethyst.commons.ui.screen.collectDisplaySettings @@ -57,6 +58,7 @@ import com.vitorpamplona.amethyst.ui.components.DefaultInlineQuoteRenderer import com.vitorpamplona.amethyst.ui.components.FlavorTranslationPlatform import com.vitorpamplona.amethyst.ui.note.platform.AndroidNotePlatform import com.vitorpamplona.amethyst.ui.platform.AndroidAppPlatform +import com.vitorpamplona.amethyst.ui.platform.AndroidAppServices val chartLightColors = VicoTheme( @@ -141,6 +143,7 @@ fun AmethystTheme( LocalTranslationPlatform provides FlavorTranslationPlatform, LocalNotePlatform provides AndroidNotePlatform, LocalAppPlatform provides AndroidAppPlatform, + LocalAppServices provides AndroidAppServices, content = content, ) } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/favorites/FavoriteCoordinate.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/favorites/FavoriteCoordinate.kt new file mode 100644 index 0000000000..1bcfb522b9 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/favorites/FavoriteCoordinate.kt @@ -0,0 +1,39 @@ +/* + * 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.favorites + +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip5aStaticWebsites.NamedSiteEvent +import com.vitorpamplona.quartz.nip5dNapplets.NamedNappletEvent + +/** + * The addressable coordinate `kind:pubkey:dtag` used to key an nsite/napplet favorite. Stored + * instead of the content hash so the favorite survives routine code/manifest updates. + */ +fun favoriteCoordinateOf(event: Event): String { + val dTag = + when (event) { + is NamedNappletEvent -> event.identifier() + is NamedSiteEvent -> event.identifier() + else -> "" + } + return "${event.kind}:${event.pubKey}:$dTag" +} 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 new file mode 100644 index 0000000000..692b4cb9fe --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/AppServices.kt @@ -0,0 +1,51 @@ +/* + * 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 + +import com.vitorpamplona.amethyst.commons.browser.BrowserHistoryRegistry +import com.vitorpamplona.amethyst.commons.browser.BrowserIconRegistry +import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionStore +import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppsRegistry +import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionLedger +import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow + +/** + * App-wide stores that outlive any one account and that screens read directly: the favorites + * row, the browser's history and favicons, what connected apps may do, and the Tor settings. + * Account-scoped services reach screens through the account's ViewModel instead. + * + * A front end implements it over its own modules; shared UI reads it through `LocalAppServices`. + */ +interface AppServices { + val favoriteApps: FavoriteAppsRegistry + + val browserHistory: BrowserHistoryRegistry + + val browserIcons: BrowserIconRegistry + + /** What each napplet/nsite has been granted, per account. */ + val nappletPermissionLedger: NappletPermissionLedger + + /** What each remote-signer client has been granted. */ + val signerPermissionStore: NostrSignerPermissionStore + + val torSettings: TorSettingsFlow +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.kt new file mode 100644 index 0000000000..89e3f1a75e --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.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 + +/** [value] with the locale's digit grouping: `1,234,567` in English, `1.234.567` in German. */ +expect fun formatGrouped(value: Long): String diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt index d596783a8e..1ef92d7047 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt @@ -23,10 +23,15 @@ package com.vitorpamplona.amethyst.commons.viewmodels import com.vitorpamplona.amethyst.commons.service.lnurl.LnurlHttpTransport import com.vitorpamplona.amethyst.commons.service.pow.PoWJobFailure import com.vitorpamplona.amethyst.commons.tor.MoneyOpRelayRouting +import com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.relay.client.auth.RelayAuthSnapshot import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebsocketBuilder +import kotlinx.collections.immutable.PersistentMap import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** * What the account's ViewModel needs from the process it runs in: app-wide services that outlive @@ -55,6 +60,12 @@ interface AccountViewModelHost { /** Routes a payment's relays under the money-operations Tor preference while it runs. */ val moneyOpRelays: MoneyOpRelayRouting + /** Which relays go through Tor under the user's settings, kept current. */ + val torRelayEvaluation: StateFlow + + /** NIP-42 authentication state per relay, across every logged-in account. */ + val relayAuthState: StateFlow> + /** Public keys of every account saved on this device. */ val savedAccounts: Flow> diff --git a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.ios.kt new file mode 100644 index 0000000000..22e852b588 --- /dev/null +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.ios.kt @@ -0,0 +1,31 @@ +/* + * 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.Foundation.NSNumber +import platform.Foundation.NSNumberFormatter +import platform.Foundation.NSNumberFormatterDecimalStyle +import platform.Foundation.numberWithLongLong + +actual fun formatGrouped(value: Long): String { + val formatter = NSNumberFormatter().apply { numberStyle = NSNumberFormatterDecimalStyle } + return formatter.stringFromNumber(NSNumber.numberWithLongLong(value)) ?: value.toString() +} diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.jvmAndroid.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.jvmAndroid.kt new file mode 100644 index 0000000000..b2e00ab2b1 --- /dev/null +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/GroupedNumber.jvmAndroid.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 java.text.NumberFormat + +actual fun formatGrouped(value: Long): String = NumberFormat.getNumberInstance().format(value) diff --git a/commonsUI/build.gradle.kts b/commonsUI/build.gradle.kts index 5073bc8d30..1ff1db16a9 100644 --- a/commonsUI/build.gradle.kts +++ b/commonsUI/build.gradle.kts @@ -189,6 +189,10 @@ kotlin { // org.jetbrains.androidx.lifecycle variant or accept a // platform-specific ViewModel access pattern on iOS. implementation(libs.androidx.lifecycle.viewmodel.compose) + + // ZXing core encodes the QR codes the shared QrCodeDrawer shows. Pure Java, + // Apache-2.0, and already linked by both apps. + implementation(libs.zxing) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.android.kt similarity index 86% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt rename to commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.android.kt index 0965ab4c75..85cd70104c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.android.kt @@ -18,23 +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.qrcode +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.platform.LocalView import com.vitorpamplona.amethyst.commons.ui.components.getActivityWindow -/** - * Pins the screen to full brightness, and awake, while a QR code is on it. - * - * Half of "this QR code will not scan" is the phone showing it. A dark-mode OLED at the - * auto-brightness the room asked for can put so little contrast between the black and white - * modules that the other camera's binarizer cannot separate them — and the person holding it has - * no idea that is the problem, because to a human eye the code looks perfectly clear. - */ @Composable -fun KeepScreenBrightAndAwake() { +actual fun KeepScreenBrightAndAwake() { val view = LocalView.current // NOT `(view.context as? Activity)`: under Compose the context is routinely a // ContextThemeWrapper, so that cast silently yields null and brightness never changes — diff --git a/commonsUI/src/commonMain/composeResources/values-cs/strings.xml b/commonsUI/src/commonMain/composeResources/values-cs/strings.xml index da517ef139..dc034ff669 100644 --- a/commonsUI/src/commonMain/composeResources/values-cs/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-cs/strings.xml @@ -6486,4 +6486,6 @@ Přehrát hlasovou zprávu Nahrát hlasovou zprávu Zastavit a odeslat + Hledat nebo zadat adresu + Nepojmenovaný nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-de/strings.xml b/commonsUI/src/commonMain/composeResources/values-de/strings.xml index a9ecda02cd..4f08431389 100644 --- a/commonsUI/src/commonMain/composeResources/values-de/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-de/strings.xml @@ -3034,4 +3034,6 @@ Der Lightning-Service des Empfängers unter %1$s ist nicht verfügbar. Er wurde aus der Lightning-Adresse "%2$s" berechnet. Fehler: %3$s. Überprüfen Sie, ob der Server online ist und ob die Lightning-Adresse korrekt ist Kann keine Blitzrechnung von %1$s erstellen: Element pr nicht im resultierenden JSON gefunden. Blitzrechnung konnte nicht erstellt werden. Nachricht von %1$s: %2$s + Suchen oder Adresse eingeben + Unbenanntes nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-eo/strings.xml b/commonsUI/src/commonMain/composeResources/values-eo/strings.xml index 797f4b70f0..46255b0be0 100644 --- a/commonsUI/src/commonMain/composeResources/values-eo/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-eo/strings.xml @@ -2995,4 +2995,6 @@ La Lightning-servo de la ricevanto ĉe %1$s ne disponeblas. Ĝi estis kalkulita el la Lightning-adreso „%2$s". Eraro: %3$s. Kontrolu ĉu la servilo funkcias kaj ĉu la Lightning-adreso estas ĝusta Ne eblas krei Lightning-fakturon de %1$s: Elemento pr ne trovita en la rezulta JSON. Ne eblas krei Lightning-fakturon. Mesaĝo de %1$s: %2$s + Serĉi aŭ enigi adreson + Sennoma nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-es/strings.xml b/commonsUI/src/commonMain/composeResources/values-es/strings.xml index c3ca94cced..c278f19263 100644 --- a/commonsUI/src/commonMain/composeResources/values-es/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-es/strings.xml @@ -2995,4 +2995,6 @@ El servicio de Lightning del destinatario en %1$s no está disponible. Se calculó a partir de la dirección de Lightning "%2$s". Error: %3$s. Comprueba si el servidor está activo y si la dirección de Lightning es correcta. No se pudo crear una factura de Lightning desde %1$s: no se encontró el elemento pr en el JSON resultante. No se pudo crear una factura de Lightning. Mensaje de %1$s: %2$s. + Buscar o introducir dirección + NApplet sin título diff --git a/commonsUI/src/commonMain/composeResources/values-fa/strings.xml b/commonsUI/src/commonMain/composeResources/values-fa/strings.xml index 29be72a76e..318f5e5a88 100644 --- a/commonsUI/src/commonMain/composeResources/values-fa/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-fa/strings.xml @@ -2995,4 +2995,6 @@ خدمات لایتنینگ %1$s گیرنده دردسترس نیست. از آدرس لایتنینگ "%2$s" محاسبه شد. خطا: %3$s. بررسی کنید آیا سرور کار می کند و آیا آدرس لایتنینگ صحیح است. نمی توان صورتحساب لایتنینگ از %1$s ساخت. المنت pr در JSON بدست آمده یافت نشد. صورت حساب لایتنینگ ساخته نشد. پیغام از %1$s: %2$s + جستجو یا وارد کردن آدرس + nApplet بی‌عنوان diff --git a/commonsUI/src/commonMain/composeResources/values-fr/strings.xml b/commonsUI/src/commonMain/composeResources/values-fr/strings.xml index 136defe6e8..81ae75c3a4 100644 --- a/commonsUI/src/commonMain/composeResources/values-fr/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-fr/strings.xml @@ -2995,4 +2995,6 @@ Le service lightning du récepteur à %1$s n'est pas disponible. Il a été calculé à partir de l'adresse lightning "%2$s". Erreur: %3$s. Vérifiez si le serveur est en ligne et si l'adresse lightning est correcte Impossible de créer une facture lightning avec %1$s. Élément pr introuvable dans le JSON résultant. Impossible de créer une facture lightning. Message de %1$s: %2$s + Rechercher ou saisir une adresse + nApplet sans titre diff --git a/commonsUI/src/commonMain/composeResources/values-in/strings.xml b/commonsUI/src/commonMain/composeResources/values-in/strings.xml index d957d12d1c..4195ff78f8 100644 --- a/commonsUI/src/commonMain/composeResources/values-in/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-in/strings.xml @@ -2958,4 +2958,6 @@ Layanan lightning penerima pada %1$s tidak tersedia. Itu dihitung dari alamat petir "%2$s". Kesalahan: %3$s. Periksa apakah server aktif dan apakah alamat lightning sudah benar Tidak dapat membuat tagihan Lightning dari %1$s: Elemen pr tidak ditemukan dalam JSON yang dihasilkan. Tidak dapat membuat tagihan Lightning. Pesan dari %1$s: %2$s + Cari atau masukkan alamat + nApplet Tanpa Judul diff --git a/commonsUI/src/commonMain/composeResources/values-ja/strings.xml b/commonsUI/src/commonMain/composeResources/values-ja/strings.xml index 7791621a8a..22a1767278 100644 --- a/commonsUI/src/commonMain/composeResources/values-ja/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-ja/strings.xml @@ -2958,4 +2958,6 @@ %1$s の受信者の Lightning サービスが利用できません。Lightning アドレス "%2$s" から計算されました。エラー: %3$s。サーバーが稼働しているか、Lightning アドレスが正しいかを確認してください %1$s から Lightning 請求書を作成できませんでした: 結果の JSON に要素 pr が見つかりませんでした。 Lightning 請求書を作成できませんでした。%1$s からのメッセージ: %2$s + 検索またはアドレスを入力 + 無題の nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-nl-rBE/strings.xml b/commonsUI/src/commonMain/composeResources/values-nl-rBE/strings.xml index f6bda663e7..9a4af42668 100644 --- a/commonsUI/src/commonMain/composeResources/values-nl-rBE/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-nl-rBE/strings.xml @@ -2995,4 +2995,6 @@ De Lightning-service van de ontvanger op %1$s is niet beschikbaar. Dit werd berekend vanuit Lightning-adres "%2$s". Fout: %3$s. Controleer of de server online is en het adres correct is. Kon geen Lightning-invoice aanmaken van %1$s: element "pr" niet gevonden in JSON. Kon geen Lightning-invoice aanmaken. Bericht van %1$s: %2$s + Zoeken of adres invoeren + Naamloze nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-nl/strings.xml b/commonsUI/src/commonMain/composeResources/values-nl/strings.xml index 2720341499..b0948cc7a7 100644 --- a/commonsUI/src/commonMain/composeResources/values-nl/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-nl/strings.xml @@ -2995,4 +2995,6 @@ De Lightning service van de ontvanger bij %1$s is niet beschikbaar. Het werd berekend op basis van het Lightning Adress "%2$s". Fout: %3$s. Controleer of de server klaar is en of het Lightning Adress juist is Kan geen Lightning invoice aanmaken van %1$s: PR-element niet gevonden in de JSON. Kan geen Lightning invoice aanmaken. Bericht van %1$s: %2$s + Zoeken of adres invoeren + Naamloze nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-ru/strings.xml b/commonsUI/src/commonMain/composeResources/values-ru/strings.xml index 7c8d0ca239..3b9e5a7240 100644 --- a/commonsUI/src/commonMain/composeResources/values-ru/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-ru/strings.xml @@ -3069,4 +3069,6 @@ Lightning-сервис получателя по адресу %1$s недоступен. Он был вычислен из Lightning-адреса "%2$s". Ошибка: %3$s. Проверьте, работает ли сервер и правильно ли указан Lightning-адрес Не удалось создать Lightning-счёт от %1$s: элемент pr не найден в полученном JSON. Не удалось создать Lightning-счёт. Сообщение от %1$s: %2$s + Поиск или введите адрес + nApplet без названия diff --git a/commonsUI/src/commonMain/composeResources/values-ta/strings.xml b/commonsUI/src/commonMain/composeResources/values-ta/strings.xml index a6fa3f2130..1de7149e7e 100644 --- a/commonsUI/src/commonMain/composeResources/values-ta/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-ta/strings.xml @@ -2995,4 +2995,6 @@ %1$s-ல் பெறுநரின் lightning சேவை கிடைக்கவில்லை. இது lightning முகவரி "%2$s" இலிருந்து கணக்கிடப்பட்டது. பிழை: %3$s. சர்வர் இயங்குகிறதா மற்றும் lightning முகவரி சரியானதா என சரிபாருங்கள் %1$s-இலிருந்து lightning invoice உருவாக்க முடியவில்லை: முடிவு JSON-ல் pr உறுப்பு கிடைக்கவில்லை. Lightning invoice உருவாக்க முடியவில்லை. %1$s-இலிருந்து செய்தி: %2$s + தேடவும் அல்லது முகவரியை உள்ளிடவும் + தலைப்பற்ற nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-th/strings.xml b/commonsUI/src/commonMain/composeResources/values-th/strings.xml index cdb57bb5be..c7d90434d1 100644 --- a/commonsUI/src/commonMain/composeResources/values-th/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-th/strings.xml @@ -2958,4 +2958,6 @@ บริการ Lightning ของ %1$s แลจะไม่พร้อมใช้งานจาก "%2$s". Error: %3$s. โปรดตรวจสอบผู้ให้บริการ หรือที่อยู่ Lightning ให้ถูกตต้อง ไม่สามารถสร้างบิลชำระในการส่ง zap จาก %1$s เนื่องจากไม่พบ PR บน JSON ไม่สามารถสร้าง lightning invoice ข้อความจาก %1$s: %2$s + ค้นหาหรือป้อนที่อยู่ + nApplet ไม่มีชื่อ diff --git a/commonsUI/src/commonMain/composeResources/values-tr/strings.xml b/commonsUI/src/commonMain/composeResources/values-tr/strings.xml index a54077a881..152a231b8b 100644 --- a/commonsUI/src/commonMain/composeResources/values-tr/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-tr/strings.xml @@ -2995,4 +2995,6 @@ %1$s adresindeki alıcının Lightning hizmeti kullanılamıyor. Bu adres "%2$s" Lightning adresinden hesaplandı. Hata: %3$s. Sunucunun çalışıp çalışmadığını ve Lightning adresinin doğru olup olmadığını kontrol edin. %1$s adresinden Lightning faturası oluşturulamadı: Sonuçtaki JSON'da pr öğesi bulunamadı. Lightning faturası oluşturulamadı. %1$s'den gelen mesaj: %2$s + Ara veya adres gir + Başlıksız nApplet diff --git a/commonsUI/src/commonMain/composeResources/values-uk/strings.xml b/commonsUI/src/commonMain/composeResources/values-uk/strings.xml index 8e96de5dd5..28c1111f75 100644 --- a/commonsUI/src/commonMain/composeResources/values-uk/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-uk/strings.xml @@ -3069,4 +3069,6 @@ Lightning-сервіс отримувача за адресою %1$s недоступний. Він був розрахований із lightning-адреси "%2$s". Помилка: %3$s. Перевірте, чи працює сервер і чи правильна lightning-адреса Неможливо створити lightning-рахунок від %1$s: Елемент pr не знайдено у відповідному JSON. Неможливо створити lightning-рахунок. Повідомлення від %1$s: %2$s + Пошук або введіть адресу + NApplet без назви diff --git a/commonsUI/src/commonMain/composeResources/values-zh/strings.xml b/commonsUI/src/commonMain/composeResources/values-zh/strings.xml index 6d7ae350e0..3ae6e070a5 100644 --- a/commonsUI/src/commonMain/composeResources/values-zh/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values-zh/strings.xml @@ -2999,4 +2999,6 @@ %1$s 的接收者闪电服务不可用。它是根据雷电地址"%2$s"计算的。 错误: %3$s。请检查服务器是否已经上线,闪电地址是否正确 无法从 %1$s 创建闪电发票:在生成的 JSON 中找不到付款要求元素。 无法创建闪电发票。来自 %1$s 的消息:%2$s + 搜索或输入地址 + 未命名nApplet diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml index e50c6fd0f3..04871363cd 100644 --- a/commonsUI/src/commonMain/composeResources/values/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values/strings.xml @@ -6243,4 +6243,6 @@ Play voice note Record a voice note Stop and send + Search or enter address + Untitled nApplet 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 4fd7c0991c..fb0e9ad03b 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 @@ -24,9 +24,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ScanOutcome import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel +import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer /** * The app shell's pieces that shared screens embed but only the front end can draw yet: the @@ -64,6 +67,42 @@ interface AppPlatform { modifier: Modifier, ) {} + /** The camera QR scanner, full screen. [onScan] says whether the payload was used. */ + @Composable + fun QrCodeScannerDialog( + onDismiss: () -> Unit, + onScan: (String) -> ScanOutcome, + ) {} + + /** Opens favorite apps and web links in the platform's app surface. */ + @Composable + fun rememberAppLauncher(): AppLauncher = AppLauncher.None + + /** + * The image model for a CORD-02 community picture: the plain URL for a url-only pointer, or a + * local copy of the decrypted blob for an encrypted one (null while it loads or if it fails). + */ + @Composable + fun rememberConcordImageModel( + pointer: ImagePointer?, + accountViewModel: AccountViewModel, + ): String? = pointer?.takeIf { !it.isResolvable() }?.url?.ifBlank { null } + + /** The cached icon of the napplet or nsite at [coordinate], if one was fetched. */ + @Composable + fun rememberNappletIconModel(coordinate: String): String? = null + + /** The icon a napplet manifest by [author] / [identifier] declares, if cached. */ + @Composable + fun rememberManifestIconModel( + author: String, + identifier: String, + ): String? = null + + /** The favicon the browser captured for [url]'s host, if any. */ + @Composable + fun rememberWebAppIconModel(url: String): String? = null + /** Draws nothing: previews, and front ends still wiring their pieces. */ object None : AppPlatform } @@ -96,3 +135,61 @@ fun GeohashLocationPickerContent( onConfirm: (String) -> Unit, modifier: Modifier = Modifier, ) = LocalAppPlatform.current.GeohashLocationPickerContent(initialGeohash, confirmLabel, onConfirm, modifier) + +@Composable +fun QrCodeScannerDialog( + onDismiss: () -> Unit, + onScan: (String) -> ScanOutcome, +) = LocalAppPlatform.current.QrCodeScannerDialog(onDismiss, onScan) + +@Composable +fun rememberAppLauncher(): AppLauncher = LocalAppPlatform.current.rememberAppLauncher() + +@Composable +fun rememberConcordImageModel( + pointer: ImagePointer?, + accountViewModel: AccountViewModel, +): String? = LocalAppPlatform.current.rememberConcordImageModel(pointer, accountViewModel) + +@Composable +fun rememberNappletIconModel(coordinate: String): String? = LocalAppPlatform.current.rememberNappletIconModel(coordinate) + +@Composable +fun rememberManifestIconModel( + author: String, + identifier: String, +): String? = LocalAppPlatform.current.rememberManifestIconModel(author, identifier) + +@Composable +fun rememberWebAppIconModel(url: String): String? = LocalAppPlatform.current.rememberWebAppIconModel(url) + +/** + * Opens a favorite app or a web link in the platform's app surface (on Android, the sandboxed + * full-screen WebView in its own task). + */ +interface AppLauncher { + /** [stillLoading] is shown when a Nostr app's defining event has not arrived yet. */ + fun launch( + app: FavoriteApp, + stillLoading: String, + ) + + /** [preferTor] forces Tor when available, e.g. for `.onion` hosts. */ + fun launchUrl( + url: String, + preferTor: Boolean = false, + ) + + /** Launches nothing: front ends without an app surface yet. */ + object None : AppLauncher { + override fun launch( + app: FavoriteApp, + stillLoading: String, + ) {} + + override fun launchUrl( + url: String, + preferTor: Boolean, + ) {} + } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/LocalAppServices.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/LocalAppServices.kt new file mode 100644 index 0000000000..b691c37bf7 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/LocalAppServices.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.amethyst.commons.ui.platform + +import androidx.compose.runtime.staticCompositionLocalOf +import com.vitorpamplona.amethyst.commons.service.AppServices + +/** + * The front end's [AppServices], installed at the composition root. No default: a screen that + * reads it outside an app root is a wiring bug, not something to paper over with empty stores. + */ +val LocalAppServices = staticCompositionLocalOf { error("No AppServices installed at the composition root") } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt new file mode 100644 index 0000000000..f87c641378 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.kt @@ -0,0 +1,34 @@ +/* + * 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.ui.screen.loggedIn.qrcode + +import androidx.compose.runtime.Composable + +/** + * Pins the screen to full brightness, and awake, while a QR code is on it. + * + * Half of "this QR code will not scan" is the phone showing it. A dark-mode OLED at the + * auto-brightness the room asked for can put so little contrast between the black and white + * modules that the other camera's binarizer cannot separate them — and the person holding it has + * no idea that is the problem, because to a human eye the code looks perfectly clear. + */ +@Composable +expect fun KeepScreenBrightAndAwake() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt similarity index 78% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt index faf17efbf8..d9de803a77 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeDrawer.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrCodeDrawer.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.qrcode +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode import androidx.compose.foundation.Canvas import androidx.compose.foundation.background @@ -42,11 +42,6 @@ import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.drawscope.DrawScope import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.google.zxing.EncodeHintType -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel -import com.google.zxing.qrcode.encoder.ByteMatrix -import com.google.zxing.qrcode.encoder.Encoder -import com.google.zxing.qrcode.encoder.QRCode import com.vitorpamplona.amethyst.commons.ui.theme.QuoteBorder import kotlin.math.min @@ -79,7 +74,7 @@ fun QrCodeDrawer( contents: String, modifier: Modifier = Modifier, ) { - val qrCode = remember(contents) { createQrCode(contents = contents) } + val matrix = remember(contents) { encodeQrMatrix(contents) } val foregroundColor = Color.Black @@ -91,64 +86,60 @@ fun QrCodeDrawer( .aspectRatio(1f) .background(Color.White), ) { - Canvas(modifier = Modifier.fillMaxSize()) { - // Calculate the height and width of each column/row - // Solve for the module size with the quiet zone measured in modules, so the whole code - // (zone included) is exactly as wide as the canvas. - val rowHeight = size.height / (qrCode.matrix.height + QR_QUIET_ZONE_MODULES * 2f) - val columnWidth = size.width / (qrCode.matrix.width + QR_QUIET_ZONE_MODULES * 2f) - // Scale the rounding with the module size. A fixed 20px radius is a gentle touch on - // a large code and a serious deformation on a small one: the finder patterns are what - // a decoder locates first, and rounding away a third of a module's worth of their - // corners is exactly the kind of damage that makes a code readable on screen and - // unreadable in a photo of that screen. - val radius = CornerRadius(min(columnWidth, rowHeight) * FINDER_CORNER_RADIUS_MODULES) - - // Draw all of the finder patterns required by the QR spec. Calculate the ratio - // of the number of rows/columns to the width and height - drawQrCodeFinders( - quietZonePx = columnWidth * QR_QUIET_ZONE_MODULES, - sideLength = size.width, - finderPatternSize = - Size( - width = columnWidth * FINDER_PATTERN_ROW_COUNT, - height = rowHeight * FINDER_PATTERN_ROW_COUNT, - ), - color = foregroundColor, - cornerRadius = radius, - ) - - // Draw data bits (encoded data part) - drawAllQrCodeDataBits( - quietZonePx = columnWidth * QR_QUIET_ZONE_MODULES, - bytes = qrCode.matrix, - size = - Size( - width = columnWidth, - height = rowHeight, - ), - color = foregroundColor, - ) + if (matrix != null) { + QrCanvas(matrix, foregroundColor) } } } -private typealias Coordinate = Pair +@Composable +private fun QrCanvas( + matrix: QrMatrix, + foregroundColor: Color, +) { + Canvas(modifier = Modifier.fillMaxSize()) { + // Calculate the height and width of each column/row + // Solve for the module size with the quiet zone measured in modules, so the whole code + // (zone included) is exactly as wide as the canvas. + val rowHeight = size.height / (matrix.height + QR_QUIET_ZONE_MODULES * 2f) + val columnWidth = size.width / (matrix.width + QR_QUIET_ZONE_MODULES * 2f) + // Scale the rounding with the module size. A fixed 20px radius is a gentle touch on + // a large code and a serious deformation on a small one: the finder patterns are what + // a decoder locates first, and rounding away a third of a module's worth of their + // corners is exactly the kind of damage that makes a code readable on screen and + // unreadable in a photo of that screen. + val radius = CornerRadius(min(columnWidth, rowHeight) * FINDER_CORNER_RADIUS_MODULES) -private fun createQrCode(contents: String): QRCode { - require(contents.isNotEmpty()) + // Draw all of the finder patterns required by the QR spec. Calculate the ratio + // of the number of rows/columns to the width and height + drawQrCodeFinders( + quietZonePx = columnWidth * QR_QUIET_ZONE_MODULES, + sideLength = size.width, + finderPatternSize = + Size( + width = columnWidth * FINDER_PATTERN_ROW_COUNT, + height = rowHeight * FINDER_PATTERN_ROW_COUNT, + ), + color = foregroundColor, + cornerRadius = radius, + ) - return Encoder.encode( - contents, - ErrorCorrectionLevel.Q, - mapOf( - EncodeHintType.CHARACTER_SET to "UTF-8", - EncodeHintType.MARGIN to QR_QUIET_ZONE_MODULES, - EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.Q, - ), - ) + // Draw data bits (encoded data part) + drawAllQrCodeDataBits( + quietZonePx = columnWidth * QR_QUIET_ZONE_MODULES, + bytes = matrix, + size = + Size( + width = columnWidth, + height = rowHeight, + ), + color = foregroundColor, + ) + } } +private typealias Coordinate = Pair + fun newPath(withPath: Path.() -> Unit) = Path().apply { fillType = PathFillType.EvenOdd @@ -157,7 +148,7 @@ fun newPath(withPath: Path.() -> Unit) = fun DrawScope.drawAllQrCodeDataBits( quietZonePx: Float, - bytes: ByteMatrix, + bytes: QrMatrix, size: Size, color: Color, ) { @@ -197,7 +188,7 @@ fun DrawScope.drawAllQrCodeDataBits( val newSize = Size(size.width + 0.5f, size.height + 0.5f) for (y in section.first.second until section.second.second) { for (x in section.first.first until section.second.first) { - if (bytes[x, y] == 1.toByte()) { + if (bytes[x, y]) { drawPath( color = color, path = diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.kt new file mode 100644 index 0000000000..359f1d16c1 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.kt @@ -0,0 +1,39 @@ +/* + * 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.ui.screen.loggedIn.qrcode + +/** The dark/light modules of an encoded QR code, row-major, without its quiet zone. */ +class QrMatrix( + val width: Int, + val height: Int, + private val modules: BooleanArray, +) { + operator fun get( + x: Int, + y: Int, + ): Boolean = modules[y * width + x] +} + +/** + * Encodes [contents] (UTF-8, error correction Q) into its module grid, or null when this + * platform has no encoder yet or the payload does not fit a QR code. + */ +expect fun encodeQrMatrix(contents: String): QrMatrix? diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/ScanOutcome.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/ScanOutcome.kt new file mode 100644 index 0000000000..e3fcb38532 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/ScanOutcome.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.amethyst.commons.ui.screen.loggedIn.qrcode + +/** What the caller did with a decoded payload — and therefore what the scanner does next. */ +enum class ScanOutcome { + /** The caller acted on it. Close the scanner. */ + Handled, + + /** Decoded fine, but this screen has nothing to do with it. Explain, and keep scanning. */ + NotSupported, +} diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.ios.kt new file mode 100644 index 0000000000..a144adff19 --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.ios.kt @@ -0,0 +1,27 @@ +/* + * 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.ui.screen.loggedIn.qrcode + +import androidx.compose.runtime.Composable + +/** Not wired on iOS yet. */ +@Composable +actual fun KeepScreenBrightAndAwake() {} diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.ios.kt new file mode 100644 index 0000000000..a7052a9868 --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.ios.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.ui.screen.loggedIn.qrcode + +/** No encoder on iOS yet: the drawer shows its blank frame. */ +actual fun encodeQrMatrix(contents: String): QrMatrix? = null diff --git a/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.jvmAndroid.kt b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.jvmAndroid.kt new file mode 100644 index 0000000000..2c60a855c0 --- /dev/null +++ b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrMatrix.jvmAndroid.kt @@ -0,0 +1,52 @@ +/* + * 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.ui.screen.loggedIn.qrcode + +import com.google.zxing.EncodeHintType +import com.google.zxing.WriterException +import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel +import com.google.zxing.qrcode.encoder.Encoder + +actual fun encodeQrMatrix(contents: String): QrMatrix? { + if (contents.isEmpty()) return null + + val code = + try { + Encoder.encode( + contents, + ErrorCorrectionLevel.Q, + mapOf( + EncodeHintType.CHARACTER_SET to "UTF-8", + EncodeHintType.MARGIN to QR_QUIET_ZONE_MODULES, + EncodeHintType.ERROR_CORRECTION to ErrorCorrectionLevel.Q, + ), + ) + } catch (e: WriterException) { + return null + } + + val matrix = code.matrix + return QrMatrix( + width = matrix.width, + height = matrix.height, + modules = BooleanArray(matrix.width * matrix.height) { matrix[it % matrix.width, it / matrix.width] == 1.toByte() }, + ) +} diff --git a/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt index 4c9a3d0b40..a7b91243e5 100644 --- a/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt +++ b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt @@ -39,6 +39,8 @@ import com.vitorpamplona.amethyst.commons.service.lnurl.LnurlHttpTransport import com.vitorpamplona.amethyst.commons.service.pow.PoWJobFailure import com.vitorpamplona.amethyst.commons.state.UiSettingsState import com.vitorpamplona.amethyst.commons.tor.MoneyOpRelayRouting +import com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation +import com.vitorpamplona.amethyst.commons.tor.TorRelaySettings import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow import com.vitorpamplona.amethyst.commons.tor.TorType import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -46,15 +48,20 @@ import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.relay.client.EmptyNostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayOfflineTracker import com.vitorpamplona.quartz.nip01Core.relay.client.auth.EmptyIAuthStatus +import com.vitorpamplona.quartz.nip01Core.relay.client.auth.RelayAuthSnapshot import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.BasicOkHttpWebSocket import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder import com.vitorpamplona.quartz.nip05DnsIdentifiers.EmptyNip05Client import com.vitorpamplona.quartz.nip60Cashu.mintApi.OkHttpMintTransport import com.vitorpamplona.quartz.utils.Hex +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flowOf import okhttp3.OkHttpClient @@ -189,6 +196,8 @@ private object PreviewAccountViewModelHost : AccountViewModelHost { override val localBlossomCacheAvailable: Flow = flowOf(false) override val powPublishFailures: Flow = emptyFlow() override val relayStats = RelayStats(EmptyNostrClient()) + override val torRelayEvaluation: StateFlow = MutableStateFlow(TorRelayEvaluation(TorRelaySettings())) + override val relayAuthState: StateFlow> = MutableStateFlow(persistentMapOf()) override val websocketBuilder = BasicOkHttpWebSocket.Builder { OkHttpClient() } override val lnurlTransport = LnurlHttpTransport { throw IllegalStateException("Previews do not reach the network") } override val moneyOpRelays = MoneyOpRelayRouting.None diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.jvm.kt new file mode 100644 index 0000000000..3412c7355b --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/KeepScreenBrightAndAwake.jvm.kt @@ -0,0 +1,27 @@ +/* + * 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.ui.screen.loggedIn.qrcode + +import androidx.compose.runtime.Composable + +/** Desktop monitors do not dim for a QR code the way a phone does; nothing to pin. */ +@Composable +actual fun KeepScreenBrightAndAwake() {} diff --git a/tools/strings-migrate/__pycache__/fix_escapes.cpython-311.pyc b/tools/strings-migrate/__pycache__/fix_escapes.cpython-311.pyc index c700f979382f32152d995cd486765698f0b55997..8e90cd5b8b852b0c6eadfb710621e327ef368479 100644 GIT binary patch delta 5646 zcmaJlYit`wdb{M3l=#-mdfD=bvJ%meWl478I&$PwB-?p7^3|5ljwP8~a#s>9YL_>= zv@D$rvW4#=3&PpSHn1*8VD@mA^CiaBXi&s=$f1`a*SZfBqRK+T0tN~c`H>&j3h)&O z{G;vn&60ZMa0Bi)voqg(^Ue2OzIX5!=h}Ye^SL=_!$0`dl^-^6+<)Rrsg)N8PpWNi zx82X!IGMj9loln|hdjr943E_=xp%CW?O69nyQOm3E4ktE$lfuh!jLA8!}4=rMerOY`aol@IB8n-U z#MNX{Olh)4U4q$r4dviD<}%n1q8R zS%D)-GNAx-$hJJDQaE)&oknDZ^eGZm6A}?M#ttI31Gf_Fbu$h!nv9DgNy?&>P{tW8 z5~H#>Nm9u)Tsbj5ps*l-3=YlA_@bGBg3g*5Wjg0r|q!k_or~3j0}&&~%c?4`hp%5_O=e z=pu@FD|ad-x)^IOGNdZW8G-^#iZg0jCn>5*={OK=6Nw}z6ETpOE)zwbMj3#rL8J*? zmoo|(QGlUWk-APrMN5L@L2?P;E2*hPy=}XQsGoZ z)xm$*$nIwXNW9~7mWqfgEp7eK!LjI2KA+Ym-dpq zkZCoYl!!#dX=Jf2M5Yq*6)i53StT*4eDR~-{`pLZ#AGc2iU{fQl&H^aT?%)&2R6ka zOGiuRf}}#sGD2bCHKFNf*PFKkUR29+L@=;PIi(xJj(^L{)(w*peRTiu2pv&ifNwY) z-9OuO%o4~*Z1{5T=>CzIB2LLKh1!NMhen?ZhfD!Y-E>;QGo4h{(^QEw*DpFeMlm}) zPwF6ky~W+(B>pCMm7}(s+&NB1BxA+1r){1a<0adTvYXt8u>0c;3JcsE|7Wo|Ub0KV z_igo@(GYAh{@dfXYc)5Gd`0bU$+10#;0DPl*|#D)0h!AQ#^=te4EN0q$Vu2Wd$$iv zc!r6KX>Ut}m;i5M_JWqYp~WP)ga%e8$q)r}@Zt`)n?M`3#4%cmMUp%wrjz;s@F;N{ z+`X@Y&5Gbeu1Cp$eVU#D^xM^Zw$Q_LCZ27U~;r@ZXp}yhxMlt{aqKK>DdGZ4Q z@A7xJIWC8HHGby_x+UI<#9FJ7_0^&1Ms)|^728dIyZDM>+a}o*kU7Xj7^J8SlY7dO zu-$ZMx=3}?4)MaYCuJpUGx;fs3f2&fWQ~{0U(7r$JC+ouVv^W9`;4N}DKVLtl_Q%1 z?Kpu<%|&3WouFo#pOT{8jBowLh=FU;jY-sOetQ!>+%5{#Vcc^2ldLej9(Z z@AJeLiItu+%ROgu-MQ}N^B1$+6WGTMeuXljm`O8V{U5Zj!z@4D_vV@NLy-&TOuLqj znXZJ!g1PBTqFtxJVp$)yu`5uNB)dnHP(O^#>OaIit%Ic(5OoOvFud;O%BpTl%dQ6a ztp%zw^Pb0^>RT@@ytG_@ar1ZSa^4ducp}+>HJ5MMRlgRfxgERx-P^In+0{VvN}xF( zXek6*);Yd>-&$4eot}4kmb83TOQEV|e!xPG-;OVSZ#58F354>2eTBfjKZx8ju;vM5 zU#CG}Z^-ne70f&lmfEy9>Z;VrV8ER7G{_F@hNn|T(%H&i0$XXb(-U(>bFkL<$XQX* zsk=&>I8KLs#@pVeLCHSHNrL1+XDhYs{=40}z~i@)@s;*$l59hg+eysXw!d2>KY7OW z&wOUqHqu2afDEk!5Oz^7bS6Y`Fj8$gp--4)n5o$)n1%!!7gKU1B}1rJbko~6IB@pN zs{@fUgQwr5)kp?a8@5~4fOoP?0%Zh_ZP`TGSmIg){ggH%#z}azMF3z2m0VfPI%jj7 z;Ei1Q!OWL|y+8G?lFk*<`Jm>Z@bFI`(uYHzx$~sIK>G86fkI$lxoqIEr}5u+cmB=p z)vmskuD;*2KB~@l4Hmivm%sg;{O${d-52tniv`ccW#J-I=r>%SDX;?=V2%%csTu#` zEHj8t$VfXR2yShk{nuz~YZOD22zfT4=wrl^&PI4sutI^#KvR=fW$i^0ZSC48;hs{l z5bf&hg!f|9E7n2;{7lmmdKwBcC|4k$Ml-F0`?To6D0EVkr6;IUfMl@}vr4rn5EheA zZnZ-yh9U$5X$;d8E+CglC}*S@C?0gIo3@8tM4q*^wm#)s!@TLDatdOz9OkXSbOZx~ zr98S9h76pCfri=!wRt&j@Yb<~V@twPJnsn=JfUSFM1#if%YQFWwDqstq2sN9YuceK zF?ofeR2R+K&HTAErM5%h`LTS+75V^jaNLUoRI&S2AraF@+5f$RJZwEuNBmCY4HI(n#a*%xfmY>85? zwdeuC;(*IJF;nn(=7GICa)D&~CUs<@?L%PmV`P}M>pRd&^Enri4s+nY+nBc4NpRQg zI?AKia^;BK!s#`o84h+z_WQzi@6UNyYWpuwAhezA05>{vUC>db#g_9Ss)L&oa(;`I zowD7R+)QqtE-MV&~Z ztP0Q$Sea1nJ!lnLTF14iWVD^gNvP?+cfDiQRm3x$*5Iilt&e36{K?OG3~f-VL%C{2 zz(!yp958LFMi0QybV&({<(ladW03VQ7RE$P#wof75rivhLNT4JP}I!wb3OivMEu`Jn zhdGNW;JKO;-qB@BPUEvmrHFMm+7HVzJX#1WCd+;43oKr^6d3U$;Tew;*nKP15v1Qt-5_dOB7-9rtGPo}&fNQOJ*u%C$hn zeEDO4aB=)rav}La_xnfg9)Iuny%+M0-G#>P2e14(_SyJq@8C-B;3GZXJ5=Z$%KOh3 z{O7Z$82wwl3%yIvT7ki$mqCJSZf~~#v2WL^Z|{n4?@xu?g$L8i&Vb9P5N1M~djK*MT)tOQ7|;%Bu#uDw_BmkmE@$OqaBf%bU;ayMr6 z_O<5F=Hh(+ZQ+jl9rse#+rGc>b>zKGU$1%m+1IR;uu(39?69~)-+XcyWcU{Umh&x_ z@u}w(o9-|aXG8UZOR_IG=biJed43Fv>KkR(?Z#7Q$msIc@E)Th*kFj>qdW@>#xJ}L zU9=O3(Jlmd%(lRV#TKhP-+{B%brM(D_BWMO-+Q(%bj2pu}eHkYlko;xIbj9J5U>paQ0c#XXfs%P= zvsvmlTKvrt9;w|009E3be@y9*gLO+4cWd9P%~kxU;r)i(=PO=e{p|`NM;S!-LE48;RzFVj(CRYMfc@sT~BUajA z8ZsUD#Vm)*n7=ZnDJC}hIs)`ovyA02_@9EligT|4(6CM*%DuhQ}C|^6=7-Nlj~K@JRdafR!5u8 z!i33-)>jWdYIM) zDG}G1J(zYV^_b4smCUk81Oqd$Ac%DQDubF;MGKG^bc*4a1sQfl_^_LT|74PKFHOS& zcs-{bgs`w~=XrizAiS{7A$XbRc!TU3&-{mX*Zj+iuPxVh=e>st-b2~)HGkcFZ1Ku+ z{fl{jPr=`l^{hL6d~lruu%xl>UO(#|9%9{2K)n2${5ps5Heg_%)2;p?Y{d6R#rgH; z*{d~KgiTJqWeL`y%UK;8ZvmCYr@KPm_VIxwJDwfL!KI+v2}mVhwax)RE}%mupnIUP PZYS7pxN2_+Xq^8KI5Y6X delta 1656 zcmZ`(TWDNG7@j$o?Ow9GXKzW|Y?IBkd!RP0sn=R*OlmBYRGOk#tlLfIq&>OpS?8Q> zYDj4wB27!h$e^uNFb`VMmQu-!Prm4rzBE#>42V!sC`DL^g7~8Ue>N$p;J|z{=l}ni z|MLBFZj4;%EqoS@h6#-K@86jCBu>aL_^~mztZ&w?(02>hM_D5aT45HlA}nl$#{(>~ zpdW2u4Im3!(Q%DM7qp`>u!=pfii1@gtPGGE3-oBhO0vYHg7@-amRul~GLhPF$5P@Z zZ9AJ*h?TL@S1BRaVAcmqP5K*dfL57k+>l%3VLM7dpRm#_z4_)-^m^thc)jL(#`P(>_stMQ1`JnOV4qt)^ZFI9?fR2|Q9MNhaj{2g3z ziOf+xBvxbH`tCWhOvP$EBaFB~+eCZZ%!KC%Q1}-v+ahsp9{S(t5?18N1rmO3|i$e zhuP&593724Xr6U%n9AB+fcKP3#M`8;v$wUY`MmmYmz<$0k={RDTa6;tCa{ul6OF}Y>zWT||@Vs&Ec zMV_DMhDf!wclLv!&*oqA*SZ-)Yj9#TT6e0nC|)#9HT`$9r*$ z`AFS%na!m(Q<|ET0oUOkq=8{dy~6FXXGUNH4e4@CjCDO3Kp*@uvD}q5{I=GnOLm#> z1L;dE-*&ZAniRivA8*}-H_+1hyYtJy`|v2jm|#8Iv-NOLHUMB!zqBSjVm65{dr}86 zMBv()u0Y`=9pNUL;@^xnBg9azLgl44>Nr#D4dYcSP-c8lQibUKO!z+0yDj^C1cjP) z+V=P$Uf}!39R3bIJMFOQl=U1RflusqcM-6rQc7!DhH5o}uw7i-X78*8BGjxAfTccv qS@!(prt2^Ng`D`j(6PgysTu*mBe38RSn$Y(X49@mw3zQ`7~a3Rdx~-Z diff --git a/tools/strings-migrate/__pycache__/migrate.cpython-311.pyc b/tools/strings-migrate/__pycache__/migrate.cpython-311.pyc index 5e750571e75fb5d9f486196a1e5963d650639377..7d06e126e42be8071e139eae546e01591c20df68 100644 GIT binary patch delta 20 acmaDE_A-onIWI340}#l}+_{lEP8$G5)dmOv delta 20 acmaDE_A-onIWI340}y Date: Tue, 29 Sep 2026 02:59:27 +0000 Subject: [PATCH 02/20] refactor: move 15 more screens into commonsUI (step 6 wave 3) With the wave-3 seams in place these screens and their closures (47 files) have no Android-only dependency left: the browser, favorite apps, connected apps, bottom-bar settings, public chats, relay information and relay-group browse screens, the Buzz boards and invite screen, the Concord home, the backup conflict review, show-QR and the software-app detail screen. AppBottomBar, UriToRoute and the relay setup rows move with them. viewModel() becomes rememberViewModel; ThreadFeedViewModel.Factory overrides the multiplatform create(KClass, CreationExtras); helpers the app still calls lose `internal`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/napplet/NappletBrokerService.kt | 2 +- .../markdown/MarkdownMediaRenderer.kt | 2 +- .../markdown/RenderContentAsMarkdown.kt | 2 +- .../amethyst/ui/navigation/AppNavigation.kt | 33 ++++++++++--------- .../bottombars/AppNavigationRail.kt | 1 + .../ui/navigation/drawer/DrawerContent.kt | 4 +-- .../ui/navigation/drawer/DrawerSections.kt | 2 +- .../ui/platform/AndroidAppPlatform.kt | 2 +- .../loggedIn/BottomBarFeedPreloaders.kt | 2 +- .../loggedIn/backups/BackupConflictCards.kt | 9 +++++ .../chats/cordnGroup/CordnGroupInfoScreen.kt | 4 +-- .../chats/geohashChat/GeohashChatsScreen.kt | 2 +- .../metadata/ChannelMetadataScreen.kt | 2 +- .../metadata/ChannelMetadataViewModel.kt | 4 +-- .../newCommunity/NewCommunityScreen.kt | 4 +-- .../loggedIn/discover/ChannelCardCompose.kt | 2 +- .../geocaches/log/LogGeocacheFindScreen.kt | 2 +- .../loggedIn/keyBackup/AccountBackupScreen.kt | 2 +- .../loggedIn/napplets/NappletsScreen.kt | 2 +- .../loggedIn/qrcode/ScanQrImageScreen.kt | 2 +- .../loggedIn/relays/AllRelayListScreen.kt | 2 +- .../relays/blocked/BlockedRelayListView.kt | 4 +-- .../broadcast/BroadcastRelayListView.kt | 4 +-- .../common/BasicRelaySetupInfoDialog.kt | 2 ++ .../relays/common/BasicRelaySetupInfoModel.kt | 2 ++ .../relays/common/RelayListCollection.kt | 1 + .../relays/common/RelayUrlEditField.kt | 4 +++ .../relays/common/RelayZipExporter.kt | 1 + .../connected/ConnectedRelayListView.kt | 2 +- .../connected/ConnectedRelayListViewModel.kt | 2 +- .../relays/dm/AddDMRelayListDialog.kt | 2 +- .../loggedIn/relays/dm/DMRelayListView.kt | 4 +-- .../relays/feeds/RelayFeedsListView.kt | 4 +-- .../relays/indexer/IndexerRelayListView.kt | 4 +-- .../keyPackage/KeyPackageRelayListView.kt | 4 +-- .../relays/local/LocalRelayListView.kt | 4 +-- .../nip37/PrivateOutboxRelayListView.kt | 4 +-- .../relays/nip65/Nip65RelayListView.kt | 4 +-- .../relays/nip65/Nip65RelayListViewModel.kt | 4 +-- .../relays/nip86/RelayManagementScreen.kt | 2 +- .../relays/proxy/ProxyRelayListView.kt | 4 +-- .../relays/search/SearchRelayListView.kt | 4 +-- .../relays/trusted/TrustedRelayListView.kt | 4 +-- .../relays/vanish/RequestToVanishScreen.kt | 2 +- .../search/AddSearchRelayListDialog.kt | 2 +- .../loggedIn/search/SearchBarViewModel.kt | 2 +- .../ui/screen/loggedIn/search/SearchScreen.kt | 2 +- .../loggedIn/settings/DrawerSettingsScreen.kt | 11 ++++++- .../settings/cordn/CordnLinkScreen.kt | 2 +- .../settings/cordn/CordnMigrateScreen.kt | 2 +- .../settings/nip46/Nip46SignerScreen.kt | 2 +- .../loggedIn/threadview/ThreadScreen.kt | 2 +- .../wallet/AddClinkDebitWalletScreen.kt | 2 +- .../loggedIn/wallet/AddNwcWalletScreen.kt | 2 +- .../ui/screen/loggedOff/login/KeyTextField.kt | 2 +- .../vitorpamplona/amethyst/URIParserTest.kt | 6 ++-- .../amethyst/favorites/RecentSubtitleTest.kt | 2 +- .../navigation/BottomBarCategoriesTest.kt | 4 +-- .../navigation/BottomBarSettingsStateTest.kt | 4 +-- .../amethyst/navigation/DrawerSectionsTest.kt | 2 +- .../notifications/NotificationDeepLinkTest.kt | 12 +++---- .../amethyst/ui/UriToRouteTest.kt | 2 ++ .../backups/BackupPresentationLabelsTest.kt | 2 ++ .../favorites/FavoriteNostrAppPreloader.kt | 2 +- .../amethyst/commons}/ui/UriToRoute.kt | 4 +-- .../commons}/ui/navigation/QueryParameters.kt | 2 +- .../ui/navigation/bottombars/AppBottomBar.kt | 4 +-- .../bottombars/GroupBottomBarEntries.kt | 2 +- .../ui/navigation/bottombars/NavBarItem.kt | 2 +- .../backups/BackupConflictEventViews.kt | 2 +- .../backups/BackupConflictListViews.kt | 2 +- .../backups/BackupConflictPresentation.kt | 4 +-- .../backups/BackupConflictReviewScreen.kt | 2 +- .../loggedIn/backups/BackupConflictVisuals.kt | 2 +- .../screen/loggedIn/browser/BrowserScreen.kt | 13 ++++---- .../loggedIn/buzz/AgentWorkBoardScreen.kt | 6 ++-- .../loggedIn/buzz/AgentWorkBoardViewModel.kt | 4 ++- .../screen/loggedIn/buzz/BuzzInviteScreen.kt | 2 +- .../ui/screen/loggedIn/buzz/JobBoardScreen.kt | 6 ++-- .../screen/loggedIn/buzz/JobBoardViewModel.kt | 4 ++- .../ui/screen/loggedIn/buzz/RelayStatusBar.kt | 2 +- .../loggedIn/buzz/WorkflowRunBoardScreen.kt | 6 ++-- .../buzz/WorkflowRunBoardViewModel.kt | 4 ++- .../concord/ConcordHomeScreen.kt | 2 +- .../relayGroup/RelayGroupBrowseScreen.kt | 6 ++-- .../relayGroup/RelayGroupServerList.kt | 2 +- .../RenderPublicChatChannelThumb.kt | 3 +- .../loggedIn/favorites/FavoriteAppsScreen.kt | 4 +-- .../loggedIn/napplets/ConnectedAppsScreen.kt | 4 +-- ...onnectedAppsFilterAssemblerSubscription.kt | 2 +- .../NappletsFilterAssemblerSubscription.kt | 2 +- .../publicChats/PublicChatsFeedLoaded.kt | 4 +-- .../loggedIn/publicChats/PublicChatsScreen.kt | 4 +-- .../loggedIn/publicChats/PublicChatsTopBar.kt | 2 +- .../PublicChatsFilterAssemblerSubscription.kt | 2 +- .../screen/loggedIn/qrcode/QrCodeScanner.kt | 4 +-- .../ui/screen/loggedIn/qrcode/ShowQRScreen.kt | 2 +- .../loggedIn/relays/RelayInformationScreen.kt | 13 ++------ .../relays/common/BasicRelaySetupInfo.kt | 2 +- .../common/BasicRelaySetupInfoClickableRow.kt | 2 +- .../relays/common/RelayNameAndRemoveButton.kt | 2 +- .../loggedIn/relays/common/RelayStatusRow.kt | 2 +- .../relays/common/RelaySuggestionState.kt | 3 +- .../relays/common/ShowRelaySuggestionList.kt | 2 +- ...layInfoNip66FilterAssemblerSubscription.kt | 2 +- .../settings/BottomBarSettingsScreen.kt | 16 ++++----- .../settings/BottomBarSettingsState.kt | 2 +- .../screen/loggedIn/settings/NavPickerUi.kt | 2 +- .../softwareapps/SoftwareAppDetailScreen.kt | 8 ++--- .../threadview/dal/ThreadFeedViewModel.kt | 12 +++++-- 110 files changed, 226 insertions(+), 184 deletions(-) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/favorites/FavoriteNostrAppPreloader.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/UriToRoute.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/navigation/QueryParameters.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/navigation/bottombars/AppBottomBar.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/navigation/bottombars/GroupBottomBarEntries.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/navigation/bottombars/NavBarItem.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/backups/BackupConflictEventViews.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/backups/BackupConflictListViews.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/backups/BackupConflictPresentation.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/backups/BackupConflictVisuals.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/browser/BrowserScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/AgentWorkBoardScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/AgentWorkBoardViewModel.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/JobBoardScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/JobBoardViewModel.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/RelayStatusBar.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/WorkflowRunBoardScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/WorkflowRunBoardViewModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupBrowseScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupServerList.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssemblerSubscription.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssemblerSubscription.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/publicChats/PublicChatsFeedLoaded.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/publicChats/PublicChatsScreen.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/publicChats/PublicChatsTopBar.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/qrcode/QrCodeScanner.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/qrcode/ShowQRScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/RelayInformationScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/common/RelayStatusRow.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/common/RelaySuggestionState.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/common/ShowRelaySuggestionList.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/BottomBarSettingsState.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/NavPickerUi.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/threadview/dal/ThreadFeedViewModel.kt (76%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt index d5b822e42c..f091ebd073 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/napplet/NappletBrokerService.kt @@ -487,7 +487,7 @@ class NappletBrokerService : Service() { /** * Brings the main activity (a `singleInstance`) forward at the Connected Apps detail for [coordinate], - * via the in-process `connectedapp?coordinate=` deep link that [com.vitorpamplona.amethyst.ui.uriToRoute] + * via the in-process `connectedapp?coordinate=` deep link that [com.vitorpamplona.amethyst.commons.ui.uriToRoute] * resolves. Used when a full-screen sandbox surface taps "Manage permissions". */ private fun openConnectedAppDetail(coordinate: String) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt index f332aa5ba3..4054d39704 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/MarkdownMediaRenderer.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.ui.components.DisplayFullNote import com.vitorpamplona.amethyst.commons.ui.components.DisplayUser +import com.vitorpamplona.amethyst.commons.ui.fragmentHashtagOrNull import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.richtext.HashtagIcon import com.vitorpamplona.amethyst.commons.ui.richtext.checkForHashtagWithIcon @@ -48,7 +49,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.LoadedBechLink import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview import com.vitorpamplona.amethyst.ui.components.ZoomableContentView -import com.vitorpamplona.amethyst.ui.fragmentHashtagOrNull import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress import com.vitorpamplona.quartz.nip19Bech32.entities.NEmbed import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt index ba24f0c0bd..3c0ebff2d0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/markdown/RenderContentAsMarkdown.kt @@ -49,10 +49,10 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.theme.MarkdownTextStyle import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.ui.theme.markdownStyle +import com.vitorpamplona.amethyst.commons.ui.uriToRoute import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.model.UrlCachedPreviewer -import com.vitorpamplona.amethyst.ui.uriToRoute import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent import com.vitorpamplona.quartz.nip92IMeta.imetasByUrl 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 df27382ddf..45a6bc0fc9 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 @@ -62,7 +62,9 @@ import com.vitorpamplona.amethyst.commons.resources.invalid_nip19_uri_descriptio import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.LocalTabReselectCoordinator import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.TabReselectCoordinator +import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.apps.recommendations.ProfileAppRecommendationsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BackupConflictReviewScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.profile.ProfileBadgesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.default.BookmarkListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.list.ListOfBookmarkGroupsScreen @@ -70,13 +72,20 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.memb import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.membershipManagement.PostBookmarkListManagementScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.podcasts.BookmarkedPodcastsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.repositories.BookmarkedRepositoriesScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.browser.BrowserScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentPersonaEditScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentWorkBoardScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzForumPostScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzInviteScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzNewDmScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.JobBoardScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.WorkflowRunBoardScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnInvitationsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.geohashChat.GeohashTeleportScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordHomeScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordInviteScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordMembersScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupBrowseScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupDiscoveryScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupThreadsScreen @@ -87,6 +96,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.browse.B import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.list.ListOfEmojiPacksScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.EmojiPackSelectionScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.MyEmojiListScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.FavoriteAppsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.GeocacheHuntScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.NewGeocacheHuntScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.gitRepositories.GitRepositoriesScreen @@ -98,6 +108,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.list.ListOfPe import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.MusicPlaylistsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.MusicTracksScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets.ConnectedAppsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.newUser.ImportFollowListPickFollowsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.newUser.ImportFollowListSelectUserScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.nsites.NsitesScreen @@ -107,12 +118,17 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.PodcastsSc import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.PodcastAuthoringScreen 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.publicChats.PublicChatsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ShowQRScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.RelayInformationScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.eventsync.EventSyncScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.nip43.RelayMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.vanish.VanishEventsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CallSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.MessagesSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.ReactionsSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.SoftwareAppDetailScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.SoftwareAppsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.AddWalletScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuMintRecommendationsScreen @@ -121,6 +137,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletS import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.ReloadMintScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TopUpMintScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.WorkoutsScreen +import com.vitorpamplona.amethyst.commons.ui.uriToRoute import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.crashreports.DisplayCrashMessages import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.RelayAuthPromptHost @@ -153,23 +170,17 @@ import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsQrScreen import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.ArticlesScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.backups.BackupConflictReviewScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.BadgesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.award.AwardBadgeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.display.BookmarkGroupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.metadata.BookmarkGroupMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.OldBookmarkListScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.BrowserScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.WebAppScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.AgentAttestationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.AgentConsoleScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.AgentWorkBoardScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzCanvasScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzDmListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzForumThreadScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzInviteScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.JobBoardScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.WorkflowRunBoardScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarCollectionsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarReminderSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarsScreen @@ -197,14 +208,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concor import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordChannelScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordCreateScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordEditScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordHomeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordInviteLinksScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.EphemeralChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.metadata.NewEphemeralChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.PublicChatChannelScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.metadata.ChannelMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.LiveActivityChannelScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupBrowseScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupChannelListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupCreateScreen @@ -228,7 +237,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabThemeWatch import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.FavoriteAppManifestPreloader import com.vitorpamplona.amethyst.ui.screen.loggedIn.emojipacks.display.EmojiPackScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.emojipacks.list.metadata.EmojiPackMetadataScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.FavoriteAppsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.NostrAppScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.FollowPackFeedScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.list.FollowPacksScreen @@ -261,7 +269,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.AddToMusicPlaylistShe import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.NewMusicPlaylistScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.NewMusicTrackScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.ConnectedAppDetailScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.ConnectedAppsScreen 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.nests.room.lobby.NestLobbyScreen @@ -277,14 +284,11 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScree import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.ProductsScreen 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.publicChats.PublicChatsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ScanQrImageScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.ShowQRScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.redirect.LoadRedirectScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relay.RelayFeedScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relayauth.RelayAuthSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip86.RelayManagementScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.subscriptions.ActiveSubscriptionsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.vanish.RequestToVanishScreen @@ -293,7 +297,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AllSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AudioVisualizerSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.BlockedUsersScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.BottomBarSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ComposeSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.DrawerSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.HiddenWordsScreen @@ -320,7 +323,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnMigrate import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46ConnectedAppsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46SignerScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.ShortsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.softwareapps.SoftwareAppDetailScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.ThreadScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlScreen @@ -340,7 +342,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.webBookmarks.WebBookmarksSc import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.NewWorkoutScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness.MyFitnessScreen import com.vitorpamplona.amethyst.ui.screen.loggedOff.AddAccountDialog -import com.vitorpamplona.amethyst.ui.uriToRoute import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser import com.vitorpamplona.quartz.nip84Highlights.parse.SharedHighlightParser diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppNavigationRail.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppNavigationRail.kt index d34b55cf9c..53b0200551 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppNavigationRail.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppNavigationRail.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.navigation.BottomBarEntry import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.LocalTabReselectCoordinator +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.rememberBottomBarSlot import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.LoggedInUserPictureDrawer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.Nav diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt index 757f7ebc0c..b31e3de9a6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt @@ -127,6 +127,8 @@ import com.vitorpamplona.amethyst.commons.resources.status_update import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStatus import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.commons.ui.layouts.PermanentDrawerWidth +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarItemDef 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.routes.routeFor @@ -157,8 +159,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.profileContentHeaderModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.isDebug -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarItemDef import com.vitorpamplona.amethyst.ui.painterRes import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.core.HexKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt index 0e0b1353f8..e11e290113 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt @@ -33,7 +33,7 @@ import com.vitorpamplona.amethyst.commons.resources.drawer_section_feeds import com.vitorpamplona.amethyst.commons.resources.drawer_section_navigate import com.vitorpamplona.amethyst.commons.resources.drawer_section_system import com.vitorpamplona.amethyst.commons.resources.drawer_section_you -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog import org.jetbrains.compose.resources.StringResource /** 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 6c46a7b4af..cfecf51470 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 @@ -35,10 +35,10 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.amethyst.ui.navigation.topbars.AndroidAroundMeLocationLabel import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer +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 import com.vitorpamplona.amethyst.favorites.rememberWebAppIconModel as AppRememberWebAppIconModel -import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar as AppBottomBarImpl import com.vitorpamplona.amethyst.ui.note.creators.location.GeohashLocationPickerContent as AppGeohashLocationPickerContent import com.vitorpamplona.amethyst.ui.note.creators.location.GeohashLocationPickerDialog as AppGeohashLocationPickerDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel as AppRememberConcordImageModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt index 3029725769..211e7a3027 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.datasource.Mu import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.datasource.PodcastEpisodesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.datasource.PodcastsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.datasource.PollsFilterAssemblerSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats.datasource.PublicChatsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.datasource.SoftwareAppsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.datasource.WorkoutsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -51,7 +52,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilte import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.datasource.ProductsFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.publicChats.datasource.PublicChatsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.datasource.ShortsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.VideoFilterAssemblerSubscription diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt index 4517e10772..de8a201fc7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictCards.kt @@ -96,6 +96,15 @@ import com.vitorpamplona.amethyst.commons.resources.backup_conflict_title import com.vitorpamplona.amethyst.commons.resources.backup_review_changed_by_other_app import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.timeAgoNoDot +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BackupConflictReviewScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BarSegment +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.ConflictCounts +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.SplitBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.StatusTag +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.clip +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.conflictTones +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.countsOf +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.eventTypeName import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListDiff diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt index b51e0f970c..c1f1ef0a50 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt @@ -133,13 +133,13 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserS import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.feed.types.observeUserNameByHex import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SectionCollapse +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SectionExpand import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CoordinatorIdentityRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightChat import com.vitorpamplona.amethyst.commons.util.toShortDisplay import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SectionCollapse -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SectionExpand import com.vitorpamplona.quartz.cordn.spec00Coordinator.JoinRequest import com.vitorpamplona.quartz.cordn.spec01GroupMetadata.CordnGroupMetadata import com.vitorpamplona.quartz.nip01Core.core.HexKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt index e6652bca50..71955dd4e6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/geohashChat/GeohashChatsScreen.kt @@ -78,7 +78,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon /** * The dedicated top-level list of the user's joined Bitchat-interoperable location channels — the - * geohash-chat analogue of [com.vitorpamplona.amethyst.ui.screen.loggedIn.publicChats.PublicChatsScreen] + * geohash-chat analogue of [com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats.PublicChatsScreen] * (NIP-28) and the Relay Groups / Concord home screens. Reached from the drawer's "Feeds" section and * pinnable as a bottom-bar tab. Each row opens the cell's chat; the "+" opens [NewGeohashChatScreen] to * join more (near me / manual / teleport). diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt index b40634d951..e61e2e42b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt @@ -68,6 +68,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.note.LoadPublicChatChannel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -81,7 +82,6 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt index 286b7f2a6a..4fd0e53023 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt @@ -41,6 +41,8 @@ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException import com.vitorpamplona.amethyst.service.uploads.CompressorQuality @@ -49,8 +51,6 @@ import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip01Core.tags.events.ETag diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt index 105360e64f..978c94596b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt @@ -102,14 +102,14 @@ import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoClickableRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPage import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoClickableRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.RelayTag diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt index 0c396b5399..5d6d783791 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt @@ -33,12 +33,12 @@ import com.vitorpamplona.amethyst.commons.ui.note.ClickableNote import com.vitorpamplona.amethyst.commons.ui.note.LongPressToQuickAction import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.commons.ui.note.calculateBackgroundColor +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip28Chats.RenderPublicChatChannelThumb import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip51FollowSets.RenderFollowSetThumb import com.vitorpamplona.amethyst.commons.ui.theme.HalfPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.RenderLongFormThumb -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.RenderPublicChatChannelThumb import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.RenderLiveActivityThumb import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.RenderCommunitiesThumb import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.RenderContentDVMThumb diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt index 12f69392be..bbb1bef126 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt @@ -92,9 +92,9 @@ import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.rememberGeocachePalette import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.datasource.GeocachesFilterAssemblerSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.quartz.nip01Core.core.Address import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt index eae4d35095..cee705213a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/keyBackup/AccountBackupScreen.kt @@ -136,6 +136,7 @@ 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.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.BackButton import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow @@ -143,7 +144,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.ui.note.authenticate import com.vitorpamplona.amethyst.ui.note.rememberAuthPromptLabels -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton import com.vitorpamplona.quartz.nip19Bech32.toNsec import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49 import kotlinx.coroutines.CoroutineScope diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/NappletsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/NappletsScreen.kt index f28fc919f6..42cf90faf5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/NappletsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/NappletsScreen.kt @@ -43,10 +43,10 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_none_found import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets.datasource.NappletsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.napplet.NappletLauncher -import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.datasource.NappletsFilterAssemblerSubscription import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip5dNapplets.NamedNappletEvent import com.vitorpamplona.quartz.nip5dNapplets.RootNappletEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ScanQrImageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ScanQrImageScreen.kt index 66e4b35358..7563df5eed 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ScanQrImageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ScanQrImageScreen.kt @@ -56,11 +56,11 @@ import com.vitorpamplona.amethyst.commons.resources.scan_qr import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.ui.uriToRoute import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.QrImageImport import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.ZxingCppBarcodeDecoder import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.copyToClipboard -import com.vitorpamplona.amethyst.ui.uriToRoute import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt index 19d92b8115..abbf158297 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/AllRelayListScreen.kt @@ -90,6 +90,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.M3ActionSection import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.RowColSpacing @@ -104,7 +105,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.broadcast.renderBroa import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayExporter import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayListCollection import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayZipExporter -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.connected.ConnectedRelayListViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.connected.renderConnectedItems import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.dm.DMRelayListViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt index e8151d24fe..6d182feb7b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt @@ -33,16 +33,16 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @Composable fun BlockedRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt index 9c34b539c1..d4e788b58f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt @@ -33,16 +33,16 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @Composable fun BroadcastRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt index 1f440f217f..43964a6b3a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt @@ -28,6 +28,8 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoClickableRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt index b39de5b486..b91d5bb6fe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt @@ -26,6 +26,8 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.relays.ui.CountFilter import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.util.replace import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.count diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayListCollection.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayListCollection.kt index 1406ded079..e8e986423e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayListCollection.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayListCollection.kt @@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.commons.resources.search_section import com.vitorpamplona.amethyst.commons.resources.search_section_explainer import com.vitorpamplona.amethyst.commons.resources.trusted_section import com.vitorpamplona.amethyst.commons.resources.trusted_section_explainer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import org.jetbrains.compose.resources.StringResource data class RelayListCollection( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt index bc35ac9fcc..208d22a47f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt @@ -52,6 +52,10 @@ import com.vitorpamplona.amethyst.commons.resources.add_a_relay import com.vitorpamplona.amethyst.commons.resources.relay_url_not_valid import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.IRelaySuggestionState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelaySuggestionState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.ShowRelaySuggestionList import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.commons.ui.theme.HalfHorzPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayZipExporter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayZipExporter.kt index ca55f16c25..5bb043eb06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayZipExporter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayZipExporter.kt @@ -26,6 +26,7 @@ import androidx.core.content.FileProvider import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.export_relay_settings import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import java.io.File import java.io.FileOutputStream import java.util.zip.ZipEntry diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListView.kt index 062c3c4b3c..b926c58619 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListView.kt @@ -41,12 +41,12 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.active_subs_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt index 60273e00e4..23092ef734 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.connected import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.commons.model.cache.LocalCache -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt index 0da5a916ab..75a2b34815 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt @@ -45,12 +45,12 @@ import com.vitorpamplona.amethyst.commons.resources.dm_relays_not_found_examples import com.vitorpamplona.amethyst.commons.resources.dm_relays_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt index 33f942ae34..d70f4465c0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt @@ -34,16 +34,16 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt index 31e185a97d..95b795bf36 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt @@ -33,16 +33,16 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @Composable fun FavoriteRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt index cee70e71e7..00cd0a43b4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt @@ -34,16 +34,16 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt index 2ab30391a2..c9b8305782 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt @@ -34,16 +34,16 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt index e2237a6b2c..eab7549d10 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt @@ -37,6 +37,8 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.send_kind0_to_local_relay_description import com.vitorpamplona.amethyst.commons.resources.send_kind0_to_local_relay_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding @@ -44,10 +46,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SettingsCategorySpacingWithHo import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt index 9dd50e8875..e21b4c6c88 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt @@ -34,16 +34,16 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt index 11942cfbca..7d7c173979 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt @@ -34,16 +34,16 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt index fa8c1450ca..5f7fa37608 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListViewModel.kt @@ -29,10 +29,10 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.events_from_you import com.vitorpamplona.amethyst.commons.resources.events_to_you +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.util.replace import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.count import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip86/RelayManagementScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip86/RelayManagementScreen.kt index 7b69acfd25..88c6e2cf56 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip86/RelayManagementScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip86/RelayManagementScreen.kt @@ -137,6 +137,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.ObserveAndRenderNIP05VerifiedS import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.AnimateOnNewSearch import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.BackButton import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.kindDisplayName import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -147,7 +148,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SmallBorder import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.nip05 import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton import com.vitorpamplona.quartz.kinds.KindNames import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt index 7c1fde3709..eb43732e10 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt @@ -34,16 +34,16 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt index 2a3ce83c58..d76010019f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt @@ -34,16 +34,16 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayCountResult import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt index 78193498cb..8c0e2410e7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt @@ -33,16 +33,16 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @Composable fun TrustedRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt index f51e300fc1..96122af4f2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt @@ -95,6 +95,7 @@ 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.DateTimeStyle import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -103,7 +104,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl import com.vitorpamplona.quartz.utils.TimeUtils diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt index b84a61e452..030057dd11 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt @@ -45,12 +45,12 @@ import com.vitorpamplona.amethyst.commons.resources.search_relays_not_found_exam import com.vitorpamplona.amethyst.commons.resources.search_relays_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayList import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayListViewModel import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt index 91ea210353..57280cf21e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt @@ -48,7 +48,7 @@ import com.vitorpamplona.amethyst.commons.search.SearchSource import com.vitorpamplona.amethyst.commons.search.SearchState import com.vitorpamplona.amethyst.commons.search.wholeInputNip19 import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.userUriPrefixes -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.quartz.buzz.invite.BuzzInviteLink import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent import com.vitorpamplona.quartz.nip01Core.core.toHexKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt index f6ef0c23c0..77cd6269d3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt @@ -130,6 +130,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowU import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoClickableRow import com.vitorpamplona.amethyst.commons.ui.search.GroupCandidate import com.vitorpamplona.amethyst.commons.ui.search.SEARCH_PICKER_LIMIT import com.vitorpamplona.amethyst.commons.ui.search.SearchFieldState @@ -147,7 +148,6 @@ import com.vitorpamplona.amethyst.service.location.CachedReversedGeoLocations import com.vitorpamplona.amethyst.service.relayClient.searchCommand.TextSearchDataSourceSubscription import com.vitorpamplona.amethyst.ui.components.namecoin.NamecoinResolutionRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelName -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoClickableRow import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt index 43afd795ad..b1f3b72604 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt @@ -58,15 +58,24 @@ import com.vitorpamplona.amethyst.commons.resources.drawer_settings_sections import com.vitorpamplona.amethyst.commons.resources.drawer_settings_show_all import com.vitorpamplona.amethyst.commons.resources.drawer_settings_title import com.vitorpamplona.amethyst.commons.resources.drawer_settings_visible +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog 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.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CatalogCard +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CatalogRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.FlushPickerEditsOnExit +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.LeadingGlyph +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.PickerHeroCard +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.PickerSectionHeader +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.RestoreDefaultRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.TogglePill +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.rememberExpandedKeys import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSection import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionVisibility import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt index 41f3e13cd8..946af4eeaa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt @@ -69,10 +69,10 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_link_section_input import com.vitorpamplona.amethyst.commons.resources.cordn_link_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CoordinatorIdentityRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.quartz.nip01Core.core.HexKey import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt index 1d96ceee8d..c9da5eb2ec 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt @@ -83,10 +83,10 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.model.cordn.AndroidCordnBlobStore -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.quartz.cordn.appMultiDevice.CordnHandoffCode import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt index a54b850f24..d3807f25ce 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt @@ -126,9 +126,9 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import kotlinx.coroutines.launch private val LiveGreen = Color(0xFF3DDC84) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt index 3eb6c6171f..e5edcfb2be 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadScreen.kt @@ -31,10 +31,10 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.threadview.dal.ThreadFeedViewModel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.threadview.datasources.ThreadFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal.ThreadFeedViewModel @Composable fun ThreadScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt index c01f2fd456..a3ab7b2946 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt @@ -67,10 +67,10 @@ import com.vitorpamplona.amethyst.commons.ui.components.util.getText import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size24Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import kotlinx.coroutines.launch /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt index cdb05c7e33..d31670a2d0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt @@ -71,12 +71,12 @@ import com.vitorpamplona.amethyst.commons.ui.components.util.getText import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.commons.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.Size24Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.quartz.nip47WalletConnect.Nip47DeepLink import com.vitorpamplona.quartz.nip47WalletConnect.Nip47WalletConnect import kotlinx.coroutines.CancellationException diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/KeyTextField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/KeyTextField.kt index f29c9a28de..93396f75ff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/KeyTextField.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedOff/login/KeyTextField.kt @@ -53,9 +53,9 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_qr_code import com.vitorpamplona.amethyst.commons.resources.nsec_npub_hex_private_key import com.vitorpamplona.amethyst.commons.resources.show_password import com.vitorpamplona.amethyst.commons.ui.painterRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner @Composable fun KeyTextField( diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/URIParserTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/URIParserTest.kt index 1cf26a6e0c..503f78f194 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/URIParserTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/URIParserTest.kt @@ -21,9 +21,9 @@ package com.vitorpamplona.amethyst import com.vitorpamplona.amethyst.commons.model.navigation.Route -import com.vitorpamplona.amethyst.ui.connectedAppRoute -import com.vitorpamplona.amethyst.ui.isConnectedAppRoute -import com.vitorpamplona.amethyst.ui.urlRoute +import com.vitorpamplona.amethyst.commons.ui.connectedAppRoute +import com.vitorpamplona.amethyst.commons.ui.isConnectedAppRoute +import com.vitorpamplona.amethyst.commons.ui.urlRoute import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/favorites/RecentSubtitleTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/favorites/RecentSubtitleTest.kt index 988495bced..21956e5607 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/favorites/RecentSubtitleTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/favorites/RecentSubtitleTest.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.favorites -import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.recentSubtitle +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.browser.recentSubtitle import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Test diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarCategoriesTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarCategoriesTest.kt index f6ce46fa14..c91d4ec94a 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarCategoriesTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarCategoriesTest.kt @@ -20,8 +20,8 @@ */ package com.vitorpamplona.amethyst.navigation -import com.vitorpamplona.amethyst.ui.navigation.bottombars.BottomBarCategories -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.BottomBarCategories +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog import org.junit.Assert.assertEquals import org.junit.Test diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarSettingsStateTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarSettingsStateTest.kt index 9d8beb9dd6..5a9b8e7c9c 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarSettingsStateTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/BottomBarSettingsStateTest.kt @@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.commons.model.navigation.BottomBarEntry import com.vitorpamplona.amethyst.commons.model.navigation.DefaultBottomBarEntries import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem import com.vitorpamplona.amethyst.commons.model.navigation.stableKey -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.BottomBarEditing -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.BottomBarSettingsState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarEditing +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarSettingsState import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt index 49b0ed57b0..10a8563d51 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt @@ -24,7 +24,7 @@ import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId import com.vitorpamplona.amethyst.commons.model.navigation.MandatoryDrawerItems import com.vitorpamplona.amethyst.commons.model.navigation.drawerSectionIdsFromNames import com.vitorpamplona.amethyst.commons.model.navigation.toNames -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections import com.vitorpamplona.amethyst.ui.navigation.drawer.SdkGatedDrawerItems import org.junit.Assert.assertEquals diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/notifications/NotificationDeepLinkTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/notifications/NotificationDeepLinkTest.kt index 7c7a87114e..3aeff8efba 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/notifications/NotificationDeepLinkTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/notifications/NotificationDeepLinkTest.kt @@ -24,14 +24,14 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.navigation.routeFor +import com.vitorpamplona.amethyst.commons.ui.chatroomRoute +import com.vitorpamplona.amethyst.commons.ui.isChatroomRoute +import com.vitorpamplona.amethyst.commons.ui.isPrivateNoteRoute +import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor -import com.vitorpamplona.amethyst.ui.chatroomRoute -import com.vitorpamplona.amethyst.ui.isChatroomRoute -import com.vitorpamplona.amethyst.ui.isPrivateNoteRoute +import com.vitorpamplona.amethyst.commons.ui.privateNoteRoute +import com.vitorpamplona.amethyst.commons.ui.uriToRoute import com.vitorpamplona.amethyst.ui.navigation.findParameterValue -import com.vitorpamplona.amethyst.ui.navigation.findQueryParameterValue -import com.vitorpamplona.amethyst.ui.privateNoteRoute -import com.vitorpamplona.amethyst.ui.uriToRoute import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync import com.vitorpamplona.quartz.nip01Core.tags.people.PTag diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt index 8ba0523bc1..1f242080d7 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/UriToRouteTest.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.navigation.Route +import com.vitorpamplona.amethyst.commons.ui.fragmentHashtagOrNull +import com.vitorpamplona.amethyst.commons.ui.uriToRoute import io.mockk.every import io.mockk.mockk import org.junit.Assert.assertEquals diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupPresentationLabelsTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupPresentationLabelsTest.kt index bd28de71ee..3c6b83f3b6 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupPresentationLabelsTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupPresentationLabelsTest.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.backups +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.labelsWrittenBy +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.presentationOf import com.vitorpamplona.quartz.nip01Core.diff.EventDiff import com.vitorpamplona.quartz.nip01Core.diff.ListDiff import com.vitorpamplona.quartz.nip01Core.metadata.MetadataDiff diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/favorites/FavoriteNostrAppPreloader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/favorites/FavoriteNostrAppPreloader.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/favorites/FavoriteNostrAppPreloader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/favorites/FavoriteNostrAppPreloader.kt index ded8b58a2f..362234a509 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/favorites/FavoriteNostrAppPreloader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/favorites/FavoriteNostrAppPreloader.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.favorites +package com.vitorpamplona.amethyst.commons.favorites import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/UriToRoute.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/UriToRoute.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/UriToRoute.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/UriToRoute.kt index 245498a847..cd01f9babe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/UriToRoute.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/UriToRoute.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 +package com.vitorpamplona.amethyst.commons.ui import com.vitorpamplona.amethyst.commons.actions.ConcordActions import com.vitorpamplona.amethyst.commons.model.Account @@ -26,9 +26,9 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.navigation.routeFor import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeForPointer -import com.vitorpamplona.amethyst.ui.navigation.findQueryParameterValue import com.vitorpamplona.quartz.buzz.invite.BuzzInviteLink import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/QueryParameters.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParameters.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/QueryParameters.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParameters.kt index d9699b7c70..88f3856290 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/QueryParameters.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParameters.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.navigation +package com.vitorpamplona.amethyst.commons.ui.navigation /** * The value of [parameterName] in this URI's query string, without going through diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppBottomBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/AppBottomBar.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppBottomBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/AppBottomBar.kt index bf0cacf966..e60a9b8150 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/AppBottomBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/AppBottomBar.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.navigation.bottombars +package com.vitorpamplona.amethyst.commons.ui.navigation.bottombars import androidx.compose.foundation.Canvas import androidx.compose.foundation.background @@ -218,7 +218,7 @@ class BottomBarSlot( /** Resolves an entry to its live [BottomBarSlot], or null if it no longer resolves (deleted favorite, etc.). */ @Composable -internal fun rememberBottomBarSlot( +fun rememberBottomBarSlot( entry: BottomBarEntry, favoritesById: Map, accountViewModel: AccountViewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/GroupBottomBarEntries.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/GroupBottomBarEntries.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/GroupBottomBarEntries.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/GroupBottomBarEntries.kt index bd252a75eb..7f79132f37 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/GroupBottomBarEntries.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/GroupBottomBarEntries.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.navigation.bottombars +package com.vitorpamplona.amethyst.commons.ui.navigation.bottombars import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/NavBarItem.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/NavBarItem.kt index 5182e8e66c..34e54a76e6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/bottombars/NavBarItem.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/bottombars/NavBarItem.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.navigation.bottombars +package com.vitorpamplona.amethyst.commons.ui.navigation.bottombars import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictEventViews.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictEventViews.kt index dda3ca3122..276a3ecb74 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictEventViews.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictEventViews.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.backups +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictListViews.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictListViews.kt index a6a6c76d64..e9e10702f8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictListViews.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictListViews.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.backups +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictPresentation.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictPresentation.kt index f807dba8d9..c08ad14e52 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictPresentation.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictPresentation.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.backups +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable @@ -341,7 +341,7 @@ fun rememberPresentation(diff: EventDiff): DiffPresentation { } /** Every label [presentationOf] resolves through its `str` parameter for [diff]. */ -internal fun labelsWrittenBy(diff: EventDiff): List = +fun labelsWrittenBy(diff: EventDiff): List = when (diff) { is MetadataDiff -> PROFILE_FIELD_LABELS is AdvertisedRelayListDiff -> RELAY_MARKER_LABELS diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt index 30c307d28f..864a22bb1c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictReviewScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictReviewScreen.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.backups +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.clickable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictVisuals.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictVisuals.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictVisuals.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictVisuals.kt index 1d8e89a034..d3cc532872 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/backups/BackupConflictVisuals.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/backups/BackupConflictVisuals.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.backups +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/browser/BrowserScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/browser/BrowserScreen.kt index f3219d3d6d..2df7df7c1a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/BrowserScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/browser/BrowserScreen.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.browser +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.browser import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.commons.browser.OmniboxSuggestions import com.vitorpamplona.amethyst.commons.browser.SuggestedWebApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon +import com.vitorpamplona.amethyst.commons.favorites.PreloadFavoriteNostrApps import com.vitorpamplona.amethyst.commons.favorites.favoriteCoordinateOf import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -107,17 +108,17 @@ import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices import com.vitorpamplona.amethyst.commons.ui.platform.rememberAppLauncher import com.vitorpamplona.amethyst.commons.ui.platform.rememberNappletIconModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.favoriteAppItems +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets.datasource.NappletsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.nsites.datasource.NsitesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.PreloadFavoriteNostrApps -import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.favoriteAppItems -import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.datasource.NappletsFilterAssemblerSubscription import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip5aStaticWebsites.NamedSiteEvent import com.vitorpamplona.quartz.nip5aStaticWebsites.RootSiteEvent import com.vitorpamplona.quartz.nip5dNapplets.NamedNappletEvent import com.vitorpamplona.quartz.nip5dNapplets.RootNappletEvent +import com.vitorpamplona.quartz.utils.TimeUtils import org.jetbrains.compose.resources.StringResource /** How many of the most recent history entries the idle browser home surfaces under "Recent". */ @@ -240,7 +241,7 @@ private fun BrowserLauncher( if (appServices.favoriteApps.isFavorite(id)) { appServices.favoriteApps.remove(id) } else { - appServices.favoriteApps.add(FavoriteApp.WebApp(url, label.ifBlank { OmniboxInput.hostOf(url) ?: url }, System.currentTimeMillis())) + appServices.favoriteApps.add(FavoriteApp.WebApp(url, label.ifBlank { OmniboxInput.hostOf(url) ?: url }, TimeUtils.nowMillis())) } } @@ -834,7 +835,7 @@ private fun hostOf(url: String): String = OmniboxInput.hostOf(url) ?: url * host stays, because that is the part that differs. Falls back to the host when the URL has * nothing more to say. */ -internal fun recentSubtitle( +fun recentSubtitle( url: String, host: String, ): String { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentWorkBoardScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentWorkBoardScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentWorkBoardScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentWorkBoardScreen.kt index b519809b21..73ece7e012 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentWorkBoardScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentWorkBoardScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat @@ -84,13 +84,13 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.buzz.AgentWorkItem import com.vitorpamplona.amethyst.commons.model.buzz.AgentWorkKind import com.vitorpamplona.amethyst.commons.model.buzz.AgentWorkState +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel 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.LoadUser @@ -123,7 +123,7 @@ fun AgentWorkBoardScreen( ) { val me = remember(accountViewModel) { accountViewModel.account.userProfile().pubkeyHex } val canWrite = remember(accountViewModel) { accountViewModel.account.isWriteable() } - val viewModel: AgentWorkBoardViewModel = viewModel(key = "AgentWorkBoard-$relayUrl-$channelId") + val viewModel: AgentWorkBoardViewModel = rememberViewModel(key = "AgentWorkBoard-$relayUrl-$channelId") { AgentWorkBoardViewModel() } viewModel.bind(accountViewModel.account, channelId, relayUrl) DisposableEffect(channelId, relayUrl) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentWorkBoardViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentWorkBoardViewModel.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentWorkBoardViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentWorkBoardViewModel.kt index 99e32dec23..b0b105b490 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentWorkBoardViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentWorkBoardViewModel.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -46,6 +46,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -59,6 +60,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlin.concurrent.Volatile /** * Backing ViewModel for the merged **Agent work** board — one channel, both agent protocols folded diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt index 2eb9f1f022..7b4d6677fa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzInviteScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzInviteScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/JobBoardScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/JobBoardScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/JobBoardScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/JobBoardScreen.kt index e7a6992928..096ae77ae1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/JobBoardScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/JobBoardScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.RepeatMode @@ -78,7 +78,6 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -86,6 +85,7 @@ import com.vitorpamplona.amethyst.commons.model.buzz.JobState import com.vitorpamplona.amethyst.commons.model.buzz.JobView import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.buzz_job_board_title +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel 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.LoadUser @@ -120,7 +120,7 @@ fun JobBoardScreen( nav: INav, ) { val me = accountViewModel.account.userProfile().pubkeyHex - val viewModel: JobBoardViewModel = viewModel(key = "JobBoard-$relayUrl-$channelId") + val viewModel: JobBoardViewModel = rememberViewModel(key = "JobBoard-$relayUrl-$channelId") { JobBoardViewModel() } viewModel.bind(accountViewModel.account, channelId, relayUrl) DisposableEffect(channelId, relayUrl) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/JobBoardViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/JobBoardViewModel.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/JobBoardViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/JobBoardViewModel.kt index c2fd829cb3..c27663ed3c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/JobBoardViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/JobBoardViewModel.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -31,6 +31,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -38,6 +39,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch +import kotlin.concurrent.Volatile /** * Backing ViewModel for the [JobBoardScreen] — the shared backlog of one Buzz channel. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/RelayStatusBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/RelayStatusBar.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/RelayStatusBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/RelayStatusBar.kt index aa0db1c0a0..9caac24011 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/RelayStatusBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/RelayStatusBar.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.RepeatMode diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/WorkflowRunBoardScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/WorkflowRunBoardScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/WorkflowRunBoardScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/WorkflowRunBoardScreen.kt index 9084414321..8fc2325e25 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/WorkflowRunBoardScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/WorkflowRunBoardScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat @@ -92,7 +92,6 @@ import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -158,6 +157,7 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_workflow_triggered_toas import com.vitorpamplona.amethyst.commons.resources.buzz_workflow_view_pr import com.vitorpamplona.amethyst.commons.resources.buzz_workflow_waiting_for_approval import com.vitorpamplona.amethyst.commons.resources.buzz_workflow_waiting_on +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel 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.LoadUser @@ -194,7 +194,7 @@ fun WorkflowRunBoardScreen( ) { val me = remember(accountViewModel) { accountViewModel.account.userProfile().pubkeyHex } val canWrite = remember(accountViewModel) { accountViewModel.account.isWriteable() } - val viewModel: WorkflowRunBoardViewModel = viewModel(key = "WorkflowRunBoard-$relayUrl-$channelId") + val viewModel: WorkflowRunBoardViewModel = rememberViewModel(key = "WorkflowRunBoard-$relayUrl-$channelId") { WorkflowRunBoardViewModel() } viewModel.bind(accountViewModel.account, channelId, relayUrl) DisposableEffect(channelId, relayUrl) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/WorkflowRunBoardViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/WorkflowRunBoardViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/WorkflowRunBoardViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/WorkflowRunBoardViewModel.kt index 1839c82f2c..4ac01887d0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/WorkflowRunBoardViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/WorkflowRunBoardViewModel.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel @@ -45,6 +45,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -58,6 +59,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlin.concurrent.Volatile /** One selectable workflow definition (kind-30620) — its UUID `d` tag, optional name, and YAML recipe. */ @Immutable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt index 64895fab11..2c0911a6c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordHomeScreen.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.chats.publicChannels.concord +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupBrowseScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupBrowseScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupBrowseScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupBrowseScreen.kt index 0f7899954d..aa0dbde732 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupBrowseScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupBrowseScreen.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.chats.publicChannels.relayGroup +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -59,12 +59,12 @@ import com.vitorpamplona.amethyst.commons.resources.relay_group_browse_title import com.vitorpamplona.amethyst.commons.resources.relay_group_browse_your_relays import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelaySuggestionState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.ShowRelaySuggestionList import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.HalfHorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.PopupUpEffect import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelaySuggestionState -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.ShowRelaySuggestionList import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupServerList.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupServerList.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupServerList.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupServerList.kt index f8d30ed04d..9da21e6e66 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupServerList.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupServerList.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.chats.publicChannels.relayGroup +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt index c4e82b4bc5..5f2aff74c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip28Chats/RenderPublicChatChannelThumb.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.discover.nip28Chats +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip28Chats import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -73,6 +73,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.launch @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt index 374957bf64..8c18fc8b4c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/FavoriteAppsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/favorites/FavoriteAppsScreen.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.favorites +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -61,6 +61,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.browser.OmniboxInput import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppIcon +import com.vitorpamplona.amethyst.commons.favorites.PreloadFavoriteNostrApps import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.navigation.Route @@ -76,7 +77,6 @@ import com.vitorpamplona.amethyst.commons.ui.platform.rememberAppLauncher import com.vitorpamplona.amethyst.commons.ui.platform.rememberNappletIconModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.favorites.PreloadFavoriteNostrApps /** * The Favorite Apps grid: the user's pinned web clients / nsites / napplets as big launch buttons. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt index c7f9661616..e82e70f476 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/ConnectedAppsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/ConnectedAppsScreen.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.napplets +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -74,9 +74,9 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices import com.vitorpamplona.amethyst.commons.ui.platform.rememberManifestIconModel import com.vitorpamplona.amethyst.commons.ui.platform.rememberWebAppIconModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets.datasource.ConnectedAppsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.datasource.ConnectedAppsFilterAssemblerSubscription import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip19Bech32.entities.NPub diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssemblerSubscription.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssemblerSubscription.kt index c6a357b9a5..df4c549108 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/datasource/ConnectedAppsFilterAssemblerSubscription.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.napplets.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssemblerSubscription.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssemblerSubscription.kt index f2af75c0db..063aadf0dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/napplets/datasource/NappletsFilterAssemblerSubscription.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.napplets.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsFeedLoaded.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsFeedLoaded.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsFeedLoaded.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsFeedLoaded.kt index 9190b18848..898b31b9c8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsFeedLoaded.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsFeedLoaded.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.publicChats +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -55,11 +55,11 @@ import com.vitorpamplona.amethyst.commons.ui.note.ClickableNote import com.vitorpamplona.amethyst.commons.ui.note.LongPressToQuickAction import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.commons.ui.note.calculateBackgroundColor +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip28Chats.RenderPublicChatChannelThumb import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip28Chats.RenderPublicChatChannelThumb @Composable fun PublicChatsFeedLoaded( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsScreen.kt index 66bb155ece..abf099c713 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsScreen.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.publicChats +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -36,8 +36,8 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded 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.publicChats.datasource.PublicChatsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.publicChats.datasource.PublicChatsFilterAssemblerSubscription @Composable fun PublicChatsScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsTopBar.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsTopBar.kt index 423d0517c0..e960e978e6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/PublicChatsTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/PublicChatsTopBar.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.publicChats +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssemblerSubscription.kt index 14be5c7925..106cdab8f0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/publicChats/datasource/PublicChatsFilterAssemblerSubscription.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.publicChats.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrCodeScanner.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrCodeScanner.kt index 9ae17da7eb..ee525414ac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/QrCodeScanner.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/QrCodeScanner.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.qrcode +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode import androidx.compose.runtime.Composable import com.vitorpamplona.amethyst.commons.model.navigation.Route @@ -26,8 +26,8 @@ import com.vitorpamplona.amethyst.commons.qrcode.ScannedPayload import com.vitorpamplona.amethyst.commons.qrcode.classifyScannedPayload import com.vitorpamplona.amethyst.commons.ui.platform.QrCodeScannerDialog import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ScanOutcome +import com.vitorpamplona.amethyst.commons.ui.uriToRoute import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.uriToRoute import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CancellationException diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/ShowQRScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/ShowQRScreen.kt index 8a31f94ec1..ecf96cdafb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/ShowQRScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/qrcode/ShowQRScreen.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.qrcode +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/RelayInformationScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/RelayInformationScreen.kt index e566d7e6de..156b7c0547 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/RelayInformationScreen.kt @@ -20,7 +20,7 @@ */ @file:Suppress("DEPRECATION") -package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -62,7 +62,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.platform.LocalUriHandler import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontFamily @@ -167,7 +166,9 @@ import com.vitorpamplona.amethyst.commons.ui.note.graspLink import com.vitorpamplona.amethyst.commons.ui.note.nipLink import com.vitorpamplona.amethyst.commons.ui.note.timeAgoNoDot import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.BackButton import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.KindChip +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.datasource.RelayInfoNip66FilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.commons.ui.theme.Height25Modifier @@ -182,8 +183,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.warningColor import com.vitorpamplona.amethyst.commons.util.timeDiffAgoShortish import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.datasource.RelayInfoNip66FilterAssemblerSubscription import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.stats.ErrorDebugMessage import com.vitorpamplona.quartz.nip01Core.relay.client.stats.IRelayDebugMessage @@ -489,8 +488,6 @@ private fun FilterVisual( filter: Filter, index: Int, ) { - val context = LocalContext.current - OutlinedCard( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), @@ -709,8 +706,6 @@ private fun OutboxEventsCard(eventIds: Set) { @Composable private fun RenderDebugMessage(msg: IRelayDebugMessage) { - val context = LocalContext.current - Column(Modifier.padding(horizontal = 12.dp)) { Row( modifier = Modifier.padding(vertical = 6.dp), @@ -1350,8 +1345,6 @@ private fun RelayMonitorReportCard( accountViewModel: AccountViewModel, nav: INav, ) { - val context = LocalContext.current - OutlinedCard( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt index 5e42cbd9f6..5964e1d7da 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.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.relays.common +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.commons.model.User diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt index 995e4c5227..6aa2c29ac7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt index c4b8f3205f..f2320212dd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayNameAndRemoveButton.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayStatusRow.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayStatusRow.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayStatusRow.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayStatusRow.kt index c239545f84..fc54797f18 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayStatusRow.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayStatusRow.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelaySuggestionState.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelaySuggestionState.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelaySuggestionState.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelaySuggestionState.kt index 21df27772c..d6879ce7e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelaySuggestionState.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelaySuggestionState.kt @@ -18,12 +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.relays.common +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/ShowRelaySuggestionList.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/ShowRelaySuggestionList.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/ShowRelaySuggestionList.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/ShowRelaySuggestionList.kt index 81d2290cb7..6ead66081b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/ShowRelaySuggestionList.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/ShowRelaySuggestionList.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.relays.common +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common import androidx.compose.foundation.layout.Column import androidx.compose.material3.HorizontalDivider diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt index 67a4f6789e..93f8b8ff7b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.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.relays.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt index b4ca1a28cc..de8a14a3bc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BottomBarSettingsScreen.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.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState @@ -87,6 +87,13 @@ import com.vitorpamplona.amethyst.commons.resources.bottom_bar_settings_pinned import com.vitorpamplona.amethyst.commons.resources.bottom_bar_settings_pinned_empty import com.vitorpamplona.amethyst.commons.resources.bottom_bar_settings_remove import com.vitorpamplona.amethyst.commons.resources.bottom_bar_settings_reorder_hint +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.BottomBarCategories +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.GroupEntryAvatar +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.GroupEntryDisplay +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCategory +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.rememberFavoriteIconModel +import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.rememberGroupEntryDisplay 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 @@ -96,13 +103,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size22Modifier import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.navigation.bottombars.BottomBarCategories -import com.vitorpamplona.amethyst.ui.navigation.bottombars.GroupEntryAvatar -import com.vitorpamplona.amethyst.ui.navigation.bottombars.GroupEntryDisplay -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCatalog -import com.vitorpamplona.amethyst.ui.navigation.bottombars.NavBarCategory -import com.vitorpamplona.amethyst.ui.navigation.bottombars.rememberFavoriteIconModel -import com.vitorpamplona.amethyst.ui.navigation.bottombars.rememberGroupEntryDisplay import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEntry import com.vitorpamplona.quartz.nip51Lists.simpleGroupList.GroupTag import kotlinx.coroutines.flow.MutableStateFlow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsState.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BottomBarSettingsState.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsState.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BottomBarSettingsState.kt index 045c274f30..01542117a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BottomBarSettingsState.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BottomBarSettingsState.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.runtime.Stable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NavPickerUi.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NavPickerUi.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NavPickerUi.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NavPickerUi.kt index 1985b6b1cb..1775e1bfae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NavPickerUi.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/NavPickerUi.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.animation.AnimatedVisibility import androidx.compose.animation.expandVertically diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt index 8ea539a211..ce0470e02c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/softwareapps/SoftwareAppDetailScreen.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.softwareapps +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -52,7 +52,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.feeds.FeedState import com.vitorpamplona.amethyst.commons.model.AddressableNote import com.vitorpamplona.amethyst.commons.model.Note @@ -68,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.resources.nip82_section_latest_release import com.vitorpamplona.amethyst.commons.resources.nip82_section_links import com.vitorpamplona.amethyst.commons.resources.nip82_section_platforms import com.vitorpamplona.amethyst.commons.resources.nip82_section_topics +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding @@ -88,6 +88,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.types.TopicChipFlow import com.vitorpamplona.amethyst.commons.ui.note.types.VersionChip import com.vitorpamplona.amethyst.commons.ui.note.types.findAllNip82Releases import com.vitorpamplona.amethyst.commons.ui.note.types.findLatestNip82Release +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.threadview.dal.ThreadFeedViewModel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.threadview.datasources.ThreadFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -100,7 +101,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder import com.vitorpamplona.amethyst.commons.ui.thread.drawReplyLevel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal.ThreadFeedViewModel import com.vitorpamplona.quartz.experimental.nip82SoftwareApps.application.SoftwareApplicationEvent import com.vitorpamplona.quartz.nip01Core.core.Address @@ -133,7 +133,7 @@ private fun SoftwareAppDetailScreenContent( // resolves that to the application note and then walks replies. val addressTag = note.idHex val threadViewModel: ThreadFeedViewModel = - viewModel( + rememberViewModel( key = addressTag + "SoftwareAppDetailThread", factory = ThreadFeedViewModel.Factory(accountViewModel.account, addressTag), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/dal/ThreadFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/threadview/dal/ThreadFeedViewModel.kt similarity index 76% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/dal/ThreadFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/threadview/dal/ThreadFeedViewModel.kt index ea83c6277f..806a25c7a2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/dal/ThreadFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/threadview/dal/ThreadFeedViewModel.kt @@ -18,23 +18,29 @@ * 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.threadview.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.threadview.dal 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.cache.LocalCache +import com.vitorpamplona.amethyst.commons.viewmodels.thread.LevelFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.thread.ThreadFeedFilter +import kotlin.reflect.KClass class ThreadFeedViewModel( account: Account, noteId: String, -) : com.vitorpamplona.amethyst.commons.viewmodels.thread.LevelFeedViewModel(ThreadFeedFilter(account, noteId, LocalCache), LocalCache) { +) : LevelFeedViewModel(ThreadFeedFilter(account, noteId, LocalCache), LocalCache) { class Factory( val account: Account, val noteId: String, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = ThreadFeedViewModel(account, noteId) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = ThreadFeedViewModel(account, noteId) as T } } From 7913ce156ebe3d889af0e5531d9aa16704172117 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 03:32:05 +0000 Subject: [PATCH 03/20] refactor: delete the main-thread checker checkNotInMainThread was a debug-only Android assertion (Looper check behind BuildConfig.DEBUG). Once the cache, feeds and view models moved to commons it could only fire through the LocalCacheHost.assertNotMainThread hook, which is a no-op on every other front end, so it no longer guarded the shared code it was sprinkled through. The checker, its call sites and the host hook are removed; the two comments that explained a Dispatchers.IO switch by the assertion now give the real reason (compression blocks). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../references/common-utilities.md | 1 - .../amethyst/AmethystLocalCacheHost.kt | 3 -- .../amethyst/LocalPreferences.kt | 8 ++--- .../amethyst/service/MainThreadChecker.kt | 36 ------------------- .../amethyst/service/cashu/CashuParser.kt | 3 -- .../service/uploads/MediaCompressor.kt | 3 -- .../uploads/blossom/BlossomUploader.kt | 5 --- .../service/uploads/nip96/Nip96Uploader.kt | 5 --- .../amethyst/ui/screen/UserFeedViewModel.kt | 3 -- .../chats/cordnGroup/CordnGroupChatScreen.kt | 9 ++--- .../concord/ConcordImageUploader.kt | 6 ++-- .../loggedIn/settings/StringFeedViewModel.kt | 3 -- .../service/lang/LanguageTranslatorService.kt | 4 --- .../2026-09-27-one-ui-android-desktop.md | 2 +- .../commons/feeds/ChannelFeedContentState.kt | 3 -- .../commons/model/cache/CachePruner.kt | 8 ----- .../commons/model/cache/CacheSearch.kt | 6 ---- .../commons/model/cache/EventCache.kt | 2 -- .../commons/model/cache/LocalCacheHost.kt | 6 ---- .../model/nip51Lists/HiddenUsersState.kt | 2 -- .../notifications/CardFeedContentState.kt | 6 ---- .../commons/feeds/TopNavFilterState.kt | 13 ------- .../notifications/NotificationSummaryState.kt | 6 ---- .../viewmodels/AccountFeedContentStates.kt | 4 --- .../commons/viewmodels/AccountViewModel.kt | 2 -- 25 files changed, 8 insertions(+), 141 deletions(-) delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/MainThreadChecker.kt diff --git a/.claude/skills/kotlin-expert/references/common-utilities.md b/.claude/skills/kotlin-expert/references/common-utilities.md index c2e0de633e..138cc44312 100644 --- a/.claude/skills/kotlin-expert/references/common-utilities.md +++ b/.claude/skills/kotlin-expert/references/common-utilities.md @@ -47,7 +47,6 @@ Under `quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/`: - **`commons/src/commonMain/.../threading/Threading.kt`** — shared dispatchers and `CoroutineScope` helpers for commonMain code. - **`amethyst/src/main/java/.../service/CoroutinesExt.kt`** — Android-only helpers: `launchIO(block)`, `launchMain(block)` built on top of `Dispatchers.IO` / `Dispatchers.Main`. Use these in ViewModels and services to stop re-spelling the dispatcher every time. -- **`amethyst/src/main/java/.../service/MainThreadChecker.kt`** — debug assertion helper for catching main-thread misuse during dev. ## Quartz iterables & JSON diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt index a0c3ec1830..4b97f1a2f8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt @@ -24,7 +24,6 @@ import com.vitorpamplona.amethyst.commons.model.cache.FileSystemNip95BlobStore import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.cache.LocalCacheHost import com.vitorpamplona.amethyst.commons.model.cache.Nip95BlobStore -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -63,6 +62,4 @@ class AmethystLocalCacheHost( // The zap path's LNURL cache is a quartz-side singleton the outbound-zap resolver fills. override fun lnurlEndpoint(lnurlpUrl: String): LnurlEndpointInfo? = LnurlEndpointCache.get(lnurlpUrl) - - override fun assertNotMainThread() = checkNotInMainThread() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index aa9a38f897..5aaf087f22 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -64,7 +64,6 @@ import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy import com.vitorpamplona.amethyst.model.backups.BackupConflictStorage import com.vitorpamplona.amethyst.model.nip60Cashu.CashuPreferences import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.concord.cord02Community.ConcordCommunityListEvent import com.vitorpamplona.quartz.experimental.ephemChat.list.EphemeralChatListEvent import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent @@ -674,17 +673,14 @@ object LocalPreferences { } } - private fun encryptedPreferences(npub: String? = null): SharedPreferences { - checkNotInMainThread() - - return if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) { + private fun encryptedPreferences(npub: String? = null): SharedPreferences = + if (BuildConfig.DEBUG && DEBUG_PLAINTEXT_PREFERENCES) { val preferenceFile = if (npub == null) DEBUG_PREFERENCES_NAME else "${DEBUG_PREFERENCES_NAME}_$npub" Amethyst.instance.appContext.getSharedPreferences(preferenceFile, Context.MODE_PRIVATE) } else { Amethyst.instance.encryptedStorage(npub) } - } /** * Clears the preferences for a given npub, deletes the preferences xml file, and switches the diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/MainThreadChecker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/MainThreadChecker.kt deleted file mode 100644 index 229a23f3db..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/MainThreadChecker.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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.service - -import android.os.Looper -import com.vitorpamplona.amethyst.BuildConfig - -fun checkNotInMainThread() { - if (BuildConfig.DEBUG && isMainThread()) { - throw OnMainThreadException("It should not be in the MainThread") - } -} - -fun isMainThread() = Looper.myLooper() == Looper.getMainLooper() - -class OnMainThreadException( - str: String, -) : RuntimeException(str) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt index 42f09e77dd..55ef6e04b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/cashu/CashuParser.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.service.cashu import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.nip60Cashu.token.CashuToken import com.vitorpamplona.quartz.nip60Cashu.token.CashuTokenB64Parser import kotlinx.collections.immutable.ImmutableList @@ -34,8 +33,6 @@ import kotlinx.collections.immutable.toImmutableList */ class CashuParser { fun parse(cashuToken: String): GenericLoadable> { - checkNotInMainThread() - val parsed = CashuTokenB64Parser.parse(cashuToken) ?: return GenericLoadable.Error("Could not parse this cashu token") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt index 1c8bb3e12a..c72093f6e8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt @@ -26,7 +26,6 @@ import android.net.Uri import androidx.core.net.toUri import androidx.media3.common.MimeTypes import com.davotoula.lightcompressor.video.GifToMp4Converter -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.components.util.MediaCompressorFileUtils import com.vitorpamplona.quartz.utils.Log import id.zelory.compressor.Compressor @@ -50,8 +49,6 @@ class MediaCompressor { useH265: Boolean = false, convertGifToMp4: Boolean = false, ): MediaCompressorResult { - checkNotInMainThread() - // Convert GIF to MP4 if requested. The GIF converter already produces a well-compressed // H.264 MP4 so no additional video compression step is needed. if (convertGifToMp4 && contentType?.contains("gif", ignoreCase = true) == true) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt index 3367d8b7ab..7656472d23 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt @@ -31,7 +31,6 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_to_server_w import com.vitorpamplona.amethyst.commons.service.HttpStatusMessages import com.vitorpamplona.amethyst.commons.service.upload.BlossomPaymentException import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult import com.vitorpamplona.amethyst.service.uploads.PreviewMetadataCalculator import com.vitorpamplona.amethyst.service.uploads.extensionFromMimeType @@ -86,8 +85,6 @@ class BlossomUploader { useMediaEndpoint: Boolean = false, onProgress: ((bytesWritten: Long, totalBytes: Long) -> Unit)? = null, ): MediaUploadResult { - checkNotInMainThread() - val contentResolver = context.contentResolver val myContentType = contentType ?: contentResolver.getType(uri) val fileName = context.getFileName(uri) @@ -141,8 +138,6 @@ class BlossomUploader { useMediaEndpoint: Boolean = false, onProgress: ((bytesWritten: Long, totalBytes: Long) -> Unit)? = null, ): MediaUploadResult { - checkNotInMainThread() - val fileName = baseFileName ?: RandomInstance.randomChars(16) val extension = contentType?.let { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt index 5b5fa59daa..bd01fb4a5a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt @@ -32,7 +32,6 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_delete_with_messag import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_to_server_with_message import com.vitorpamplona.amethyst.commons.service.HttpStatusMessages import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.uploads.AVIF_EXTENSION import com.vitorpamplona.amethyst.service.uploads.AVIF_MIME import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult @@ -105,8 +104,6 @@ class Nip96Uploader { httpAuth: suspend (String, String, ByteArray?) -> HTTPAuthorizationEvent?, context: Context, ): MediaUploadResult { - checkNotInMainThread() - val contentResolver = context.contentResolver val myContentType = contentType ?: contentResolver.getType(uri) val length = size ?: contentResolver.querySize(uri) ?: fileSize(uri) ?: 0 @@ -145,8 +142,6 @@ class Nip96Uploader { httpAuth: suspend (String, String, ByteArray?) -> HTTPAuthorizationEvent?, context: Context, ): MediaUploadResult { - checkNotInMainThread() - val fileName = RandomInstance.randomChars(16) val extension = contentType?.let { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 7f01e6ba76..9e9e28d409 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -32,7 +32,6 @@ import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.service.BundledUpdate import com.vitorpamplona.amethyst.commons.util.equalImmutableLists -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.utils.Log import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -56,8 +55,6 @@ open class UserFeedViewModel( override val isRefreshing: MutableState = mutableStateOf(false) private fun refreshSuspended() { - checkNotInMainThread() - try { isRefreshing.value = true diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt index fba0c1263d..04b4e5f8f2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt @@ -1039,13 +1039,10 @@ private suspend fun sendAttachment( try { // The media-quality slider. // - // Off the main thread, like the strip and the read below it. + // Off the main thread, like the strip and the read below it: // `sendAttachment` is called from the composition's scope, so it - // inherits Main, and `MediaCompressor.compress` opens with - // `checkNotInMainThread()` — so every image attachment threw - // `OnMainThreadException` before it ever reached the uploader. The - // voice path never hit it because it posts its recording already - // encoded and skips compression entirely. + // inherits Main, and compression blocks. The voice path posts its + // recording already encoded and skips compression entirely. val compressed = withContext(Dispatchers.IO) { item.orchestrator.compressIfNeeded( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt index 9c3c5470ac..8d9cc8de9e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt @@ -54,10 +54,8 @@ class ConcordImageUploader( /** * Compresses, strips, AES-256-GCM-encrypts and uploads the picked [uri], returning its pointer. * - * Runs on [Dispatchers.IO]: the compression/upload pipeline asserts it is off the main thread - * ([MediaCompressor] calls `checkNotInMainThread`), and callers launch this from a Compose - * `rememberCoroutineScope()`, which is Main-dispatched — so without this switch the first step - * throws before any bytes leave the device. + * Runs on [Dispatchers.IO]: compression, encryption and the upload all block, and callers launch + * this from a Compose `rememberCoroutineScope()`, which is Main-dispatched. */ suspend fun uploadEncrypted( uri: Uri, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt index b4a28274ee..74fa8e87ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt @@ -31,7 +31,6 @@ import com.vitorpamplona.amethyst.commons.feeds.StringFeedState import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.service.BundledUpdate import com.vitorpamplona.amethyst.commons.util.equalImmutableLists -import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.quartz.utils.Log import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -55,8 +54,6 @@ open class StringFeedViewModel( } private fun refreshSuspended() { - checkNotInMainThread() - try { isRefreshing.value = true diff --git a/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt b/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt index 9f5d9f0087..9bc5ac07a9 100644 --- a/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt +++ b/amethyst/src/play/java/com/vitorpamplona/amethyst/service/lang/LanguageTranslatorService.kt @@ -30,7 +30,6 @@ import com.google.mlkit.nl.translate.TranslateLanguage import com.google.mlkit.nl.translate.Translation import com.google.mlkit.nl.translate.Translator import com.google.mlkit.nl.translate.TranslatorOptions -import com.vitorpamplona.amethyst.service.checkNotInMainThread import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors @@ -91,7 +90,6 @@ object LanguageTranslatorService { source: String, target: String, ): Task { - checkNotInMainThread() val sourceLangCode = TranslateLanguage.fromLanguageTag(source) val targetLangCode = TranslateLanguage.fromLanguageTag(target) @@ -110,8 +108,6 @@ object LanguageTranslatorService { val translator = translators[options] return translator.downloadModelIfNeeded().onSuccessTask(executorService) { - checkNotInMainThread() - val dict = TranslationDictionary.build(text) val encoded = TranslationDictionary.encode(text, dict) 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 2ee47b6ae1..ca598c88ff 100644 --- a/commons/plans/2026-09-27-one-ui-android-desktop.md +++ b/commons/plans/2026-09-27-one-ui-android-desktop.md @@ -119,7 +119,7 @@ Each leaving edge is a seam to cut before the group can move. | `Amethyst.kt` | `keyCache` (Account), `encryptedStorage` (Geohash), `appContext` (Nip46 bridge), `notificationDispatcher` (EventProcessor) | constructor parameters / ports; the notification dispatcher gets an interface. **Done 2026-09-28**: `keyCache` → `Account.encryptionKeyCache`; the notifications → `MarmotGroupNotifier`, which `NotificationDispatcher` implements; `encryptedStorage` and `appContext` left with the Geohash and Nip46 cuts. | | `LocalPreferences.kt` | `saveToEncryptedStorage(accountSettings)` on settings change | **Done 2026-09-28** as a constructor lambda, `Account.saveSettings: suspend (AccountSettings) -> Unit`, rather than a named port. | | `DebugUtils.kt` | `logTime` (2 sites) | **Done 2026-09-28.** `commons/util` already had an identical `logTime`; its Android `isDebug` was hard-coded `false`, so it now reads `AndroidDebugFlag.enabled`, which `Amethyst.onCreate` sets. The app copy is gone. | -| `service/MainThreadChecker.kt` | `checkNotInMainThread` (HiddenUsersState) | **Done 2026-09-28**: `LocalCache.appHost.assertNotMainThread()`, which the app host delegates to the same check. | +| `service/MainThreadChecker.kt` | `checkNotInMainThread` (HiddenUsersState) | **Done 2026-09-28**: `LocalCache.appHost.assertNotMainThread()`. **Deleted 2026-09-29**: a debug-only Android assertion that shared code could no longer rely on; the checker, its call sites and the host hook are gone. | | `service/location/LocationState.kt` | the `LocationResult` type in `geolocationFlow` and the around-me feed | **Done 2026-09-28**: `commons/model/location/LocationResult.kt`; 25 files repointed. | | `service/uploads/FileHeader.kt` | the `FileHeader` data type in three send methods | **Done 2026-09-28**: `commons/service/upload/FileHeader.kt` (with `BlurhashWrapper`/`ThumbhashWrapper` moved from commonsUI, same package); `prepare` stays in the app as `FileHeader.Companion` extensions. | | `service/relayClient/…/BuzzMembershipEoseManager.kt` | the `MembershipNotificationKinds` constant | **Done 2026-09-28**: `commons/model/buzz/BuzzMembershipKinds.kt`. | diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt index 296893376c..b08f4a27d2 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/ChannelFeedContentState.kt @@ -25,7 +25,6 @@ import androidx.compose.runtime.Stable import androidx.compose.runtime.mutableStateOf import com.vitorpamplona.amethyst.commons.model.Channel import com.vitorpamplona.amethyst.commons.model.Note -import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.service.BundledInsert import com.vitorpamplona.amethyst.commons.service.BundledUpdate import com.vitorpamplona.amethyst.commons.util.equalImmutableLists @@ -73,8 +72,6 @@ class ChannelFeedContentState( } fun refreshSuspended() { - LocalCache.appHost.assertNotMainThread() - isRefreshing.value = true try { lastFeedKey = localFilter.feedKey() diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt index 119f2ff33e..056c4e3018 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CachePruner.kt @@ -162,8 +162,6 @@ class CachePruner( } fun pruneOldMessages() { - cache.appHost.assertNotMainThread() - cache.ephemeralChannels.forEach { _, channel -> pruneOldMessagesChannel(channel) } @@ -310,8 +308,6 @@ class CachePruner( } fun pruneRepliesAndReactions(accounts: Set) { - cache.appHost.assertNotMainThread() - val toBeRemoved = cache.notes.filter { _, note -> ( @@ -448,8 +444,6 @@ class CachePruner( } fun pruneExpiredEvents() { - cache.appHost.assertNotMainThread() - val now = TimeUtils.now() val versionsToBeRemoved = cache.notes.filter { _, it -> it.event?.isExpirationBefore(now) == true } val addressesToBeRemoved = cache.addressables.filter { _, it -> it.event?.isExpirationBefore(now) == true } @@ -481,8 +475,6 @@ class CachePruner( * authors the reader never muted. */ fun pruneHiddenEvents(hidden: LiveHiddenUsers) { - cache.appHost.assertNotMainThread() - val childrenToBeRemoved = mutableListOf() val toBeRemoved = diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt index 6abcdaefd3..9537f83e0e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/CacheSearch.kt @@ -69,8 +69,6 @@ class CacheSearch( ): List { if (username.isBlank()) return emptyList() - cache.appHost.assertNotMainThread() - val key = decodePublicKeyAsHexOrNull(username) if (key != null) { @@ -164,8 +162,6 @@ class CacheSearch( filters: List, hidden: LiveHiddenUsers, ): List { - cache.appHost.assertNotMainThread() - if (filters.isEmpty()) return emptyList() // Distinct across filters: a union of arms (a hashtag asks #t, #l and the comment tags) @@ -201,8 +197,6 @@ class CacheSearch( text: String, hidden: LiveHiddenUsers, ): List { - cache.appHost.assertNotMainThread() - if (text.isBlank()) return emptyList() val key = decodeEventIdAsHexOrNull(text) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt index 992eb25bcf..c6615120cc 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/EventCache.kt @@ -3357,8 +3357,6 @@ open class EventCache : var verifyMeter: ((elapsedNanos: Long, valid: Boolean) -> Unit)? = null fun justVerify(event: Event): Boolean { - appHost.assertNotMainThread() - val meter = verifyMeter if (meter == null) return justVerifyInner(event) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt index e004e23b69..be7628aff7 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt @@ -98,12 +98,6 @@ interface LocalCacheHost { */ fun lnurlEndpoint(lnurlpUrl: String): LnurlEndpointInfo? = null - /** - * Throws if called on the platform's main thread. Signature verification and the cache - * sweeps are far too slow to run there. A no-op by default and on release builds. - */ - fun assertNotMainThread() {} - /** The neutral host: an own IO scope and nothing else. */ companion object Default : LocalCacheHost { override val scope: CoroutineScope by lazy { CoroutineScope(Dispatchers.IO + SupervisorJob()) } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt index 7f50f68545..bad5eaa0a9 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/nip51Lists/HiddenUsersState.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.commons.model.nip51Lists import com.vitorpamplona.amethyst.commons.model.AccountSettings import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers -import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip51Lists.muteList.tags.EventTag import com.vitorpamplona.quartz.nip51Lists.muteList.tags.HashtagTag @@ -84,7 +83,6 @@ class HiddenUsersState( settings.syncedSettings.security.showSensitiveContent, settings.syncedSettings.security.maxHashtagLimit, ) { blockList, muteList, transientHiddenUsers, showSensitiveContent, maxHashtagLimit -> - LocalCache.appHost.assertNotMainThread() emit(assembleLiveHiddenUsers(blockList, muteList, transientHiddenUsers, showSensitiveContent, maxHashtagLimit)) }.onStart { emit( diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt index 7fdd14bc57..0abaeb2f2d 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/CardFeedContentState.kt @@ -123,8 +123,6 @@ class CardFeedContentState( private fun refreshSuspended() = refreshLock.withLock { refreshLocked() } private fun refreshLocked() { - LocalCache.appHost.assertNotMainThread() - try { isRefreshing.value = true @@ -170,8 +168,6 @@ class CardFeedContentState( } private fun convertToCard(notes: Collection): List { - LocalCache.appHost.assertNotMainThread() - val reactionsPerEvent = mutableMapOf>() notes .filter { it.event is ReactionEvent } @@ -544,8 +540,6 @@ class CardFeedContentState( } fun updateFeedWith(newNotes: Set) { - LocalCache.appHost.assertNotMainThread() - if (localFilter is AdditiveFeedFilter && _feedContent.value is CardFeedState.Loaded) { invalidateInsertData(newNotes) } else { diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt index c370932351..98f0917163 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/feeds/TopNavFilterState.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.AddressableNote -import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.nip51Lists.interestSets.InterestSet import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter @@ -295,7 +294,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, globalFollow), @@ -308,7 +306,6 @@ class TopNavFilterState( private val _badgeRoutes = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, globalFollow, mineFollow), @@ -320,7 +317,6 @@ class TopNavFilterState( private val _communityRoutes = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, globalFollow, mineFollow), @@ -335,7 +331,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Same content-style catalog as kind3GlobalPeopleRoutes, plus "Mine" so the @@ -353,7 +348,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Git repository announcements can be narrowed by author, hashtag and geohash, @@ -371,7 +365,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Workout records can be narrowed by author, hashtag and geohash, so this @@ -389,7 +382,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Highlights can be narrowed by author, hashtag and geohash, so this mirrors @@ -408,7 +400,6 @@ class TopNavFilterState( liveInterestFlows, account.relayGroupList.liveRelayGroupServers, ) { peopleLists, interests, joinedServers -> - LocalCache.appHost.assertNotMainThread() // A relay chip per host relay of every group the user joined (kind-10009), so they can // browse the OTHER groups on those relays without first having to Favorite them. Deduped // against relays that are already chips because they were favorited (kind-10012, already @@ -442,7 +433,6 @@ class TopNavFilterState( livePeopleListsFlow, liveInterestFlows, ) { peopleLists, interests -> - LocalCache.appHost.assertNotMainThread() emit( listOf( // Same content-style catalog as kind3GlobalPeopleRoutes, plus "Mine" so the @@ -457,7 +447,6 @@ class TopNavFilterState( private val _kind3GlobalPeople = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, globalFollow), @@ -473,7 +462,6 @@ class TopNavFilterState( // and AroundMe — none of which can match a tag-less manifest — are deliberately left out. private val _authorOnlyRoutes = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, globalFollow, mineFollow), @@ -485,7 +473,6 @@ class TopNavFilterState( private val _notificationLists = livePeopleListsFlow.transform { peopleLists -> - LocalCache.appHost.assertNotMainThread() emit( listOf( listOf(allFollows, userFollows, kind3Follows, aroundMe, teleport, selectedFollow, globalFollow), diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt index 5dd59a4eb6..be93ba96be 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/notifications/NotificationSummaryState.kt @@ -89,8 +89,6 @@ class NotificationSummaryState( fun today(): String = LocalClock.today().ymd() suspend fun initializeSuspend() { - LocalCache.appHost.assertNotMainThread() - val days = LocalClock.epochDayCounter() val currentUser = user.pubkeyHex @@ -182,8 +180,6 @@ class NotificationSummaryState( } suspend fun addToStatsSuspend(newBlockNotes: Set>) { - LocalCache.appHost.assertNotMainThread() - val days = LocalClock.epochDayCounter() val currentUser = user.pubkeyHex @@ -286,8 +282,6 @@ class NotificationSummaryState( } private suspend fun refreshChartModel() { - LocalCache.appHost.assertNotMainThread() - val today = LocalClock.today() val dataAxisLabelIndexes = listOf(-6, -5, -4, -3, -2, -1, 0) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt index 3242eeb674..2fab0a5664 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountFeedContentStates.kt @@ -373,8 +373,6 @@ class AccountFeedContentStates( } fun updateFeedsWith(newNotes: Set) { - LocalCache.appHost.assertNotMainThread() - homeLive.updateFeedWith(newNotes) homeNewThreads.updateFeedWith(newNotes) homeReplies.updateFeedWith(newNotes) @@ -447,8 +445,6 @@ class AccountFeedContentStates( } fun deleteNotes(newNotes: Set) { - LocalCache.appHost.assertNotMainThread() - homeLive.deleteFromFeed(newNotes) homeNewThreads.deleteFromFeed(newNotes) homeReplies.deleteFromFeed(newNotes) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt index 54f5d4a7aa..3a10ba5a4f 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModel.kt @@ -694,8 +694,6 @@ class AccountViewModel( accountChoices: LiveHiddenUsers, followUsers: Set, ): NoteComposeReportState { - LocalCache.appHost.assertNotMainThread() - val isFromLoggedIn = note.author?.pubkeyHex == userProfile().pubkeyHex val isFromLoggedInFollow = note.author?.let { followUsers.contains(it.pubkeyHex) } ?: true val isPostHidden = note.isHiddenFor(accountChoices) From ce7e6d31bcb37af9da23daa23a95a4bd2f35b91a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 04:16:55 +0000 Subject: [PATCH 04/20] refactor: cut more shared-screen blockers (step 6 wave 4 seams) - @SuppressLint -> @Suppress app-wide (Lint honours both; drops the android.annotation import). - formatHistoryReachDate becomes expect/actual (NSDateFormatter template on iOS). - quartz ConcurrentSet gains clear(); the Buzz console and DM-list view models use quartz's ConcurrentSet/ConcurrentMap instead of java.util.concurrent. - HiddenAccountsFeedViewModel and ChessViewModelFactory override the multiplatform create(KClass, CreationExtras); StringFeedViewModel drops javaClass. - SettingsIcon.Painter takes a DrawableResource (relays, ic_tor are Compose drawables). - Connected-app detail labels come from Compose resources (browser permission keys copied from the synchronous tier); its OK button uses the translated "Confirm". - Share buttons on the Concord invite, people-list and follow-pack screens go through rememberTextSharer; dates and counts use formatDateTime/formatGrouped; the attestation screen's UTC echo uses SearchDate instead of SimpleDateFormat. - AppPlatform.isCastingAvailable replaces BuildConfig in the video-player settings; supportsEmbeddedAppTabs replaces the API-30 check in the drawer sections. - WalletViewModel drops an unused appContext and routes CLINK budgets through the host's moneyOpRelays; LoadRedirectScreen takes INav. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/LocalPreferences.kt | 3 +- .../amethyst/service/PackageUtils.kt | 3 +- .../amethyst/service/location/LocationFlow.kt | 7 ++-- .../amethyst/service/logging/LogMonitor.kt | 3 +- .../service/payments/PayToAppAvailability.kt | 5 ++- .../service/playback/diskCache/VideoCache.kt | 3 +- .../NWCFinderFilterAssemblerSubscription.kt | 3 +- .../ui/navigation/drawer/DrawerSections.kt | 7 ++-- .../amethyst/ui/navigation/navs/Nav.kt | 3 +- .../note/creators/polls/PollDeadlinePicker.kt | 3 +- .../note/creators/polls/PollOptionsField.kt | 3 +- .../zappolls/ZapPollConsensusThreshold.kt | 3 +- .../zappolls/ZapPollDeadlinePicker.kt | 3 +- .../note/creators/zappolls/ZapPollOption.kt | 3 +- .../zappolls/ZapPollVoteValueRange.kt | 3 +- .../ui/platform/AndroidAppPlatform.kt | 3 ++ .../old/OldBookmarkListScreen.kt | 3 +- .../loggedIn/buzz/AgentAttestationScreen.kt | 18 +++++----- .../loggedIn/buzz/AgentConsoleViewModel.kt | 6 ++-- .../loggedIn/buzz/BuzzDmListViewModel.kt | 17 ++++++---- .../chats/cordnGroup/CordnGroupInfoScreen.kt | 2 +- .../concord/ConcordChannelListScreen.kt | 13 ++------ .../concord/ConcordInviteLinksScreen.kt | 6 ++-- .../loggedIn/chess/ChessRelaySettingsSheet.kt | 3 +- .../loggedIn/chess/ChessViewModelFactory.kt | 9 +++-- .../discover/nip99Classifieds/SellProduct.kt | 3 +- .../ui/screen/loggedIn/embed/RemoteImeView.kt | 3 +- .../followPacks/feed/FollowPackFeedScreen.kt | 3 +- .../screen/loggedIn/geohash/GeoHashScreen.kt | 3 +- .../GeoHashFilterAssemblerSubscription.kt | 3 +- .../screen/loggedIn/hashtag/HashtagScreen.kt | 3 +- .../loggedIn/home/ShortNotePostScreen.kt | 3 +- .../lists/display/lists/PeopleListScreen.kt | 23 ++----------- .../lists/display/packs/FollowPackScreen.kt | 23 ++----------- .../napplets/ConnectedAppDetailScreen.kt | 33 ++++++++++++------- .../loggedIn/redirect/LoadRedirectScreen.kt | 3 +- .../screen/loggedIn/relay/RelayFeedScreen.kt | 3 +- .../settings/SettingsCatalogBuilder.kt | 7 ++-- .../loggedIn/settings/SettingsSectionCard.kt | 5 +-- .../loggedIn/settings/StringFeedViewModel.kt | 8 ++--- .../settings/VideoPlayerSettingsScreen.kt | 5 +-- .../dal/HiddenAccountsFeedViewModel.kt | 11 +++++-- .../loggedIn/threadview/ThreadFeedView.kt | 3 +- .../ui/screen/loggedIn/url/UrlScreen.kt | 3 +- .../UrlFilterAssemblerSubscription.kt | 3 +- .../loggedIn/wallet/CashuWalletScreen.kt | 16 ++++----- .../screen/loggedIn/wallet/WalletViewModel.kt | 10 ++---- .../commons/model/chats/HistoryDateFormat.kt | 27 +++++++++++++++ .../model/chats/HistoryDateFormat.ios.kt | 32 ++++++++++++++++++ ...mat.kt => HistoryDateFormat.jvmAndroid.kt} | 7 +--- .../platform/EmbeddedAppSurfaces.android.kt | 25 ++++++++++++++ .../composeResources/values/strings.xml | 7 ++++ .../commons/ui/platform/AppPlatform.kt | 3 ++ .../ui/platform/EmbeddedAppSurfaces.kt | 27 +++++++++++++++ .../commons/ui/settings/SettingsCatalog.kt | 3 +- .../ui/platform/EmbeddedAppSurfaces.ios.kt | 23 +++++++++++++ .../ui/platform/EmbeddedAppSurfaces.jvm.kt | 23 +++++++++++++ .../ui/settings/SettingsCatalogFilterTest.kt | 2 +- .../quartz/utils/concurrent/ConcurrentSet.kt | 2 ++ .../concurrent/ConcurrentSet.jvmAndroid.kt | 2 ++ .../utils/concurrent/ConcurrentSet.native.kt | 2 ++ 61 files changed, 310 insertions(+), 187 deletions(-) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.kt create mode 100644 commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.ios.kt rename commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/chats/{HistoryDateFormat.kt => HistoryDateFormat.jvmAndroid.kt} (74%) create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.android.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.ios.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.jvm.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt index 5aaf087f22..16f25fc259 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst -import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences import androidx.compose.runtime.Immutable @@ -690,7 +689,7 @@ object LocalPreferences { * deleted. If we use `apply()` there is a race condition and the file will probably not be * deleted */ - @SuppressLint("ApplySharedPref") + @Suppress("ApplySharedPref") suspend fun deleteAccount(accountInfo: AccountInfo) { Log.d("LocalPreferences") { "Saving to encrypted storage updatePrefsForLogout ${accountInfo.npub}" } withContext(Dispatchers.IO) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt index a25b3e85d0..70ef33c162 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/PackageUtils.kt @@ -20,11 +20,10 @@ */ package com.vitorpamplona.amethyst.service -import android.annotation.SuppressLint import android.content.Context object PackageUtils { - @SuppressLint("QueryPermissionsNeeded") + @Suppress("QueryPermissionsNeeded") private fun isPackageInstalled( context: Context, target: String, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/location/LocationFlow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/location/LocationFlow.kt index 0fd483cf2a..171183e9a4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/location/LocationFlow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/location/LocationFlow.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.service.location -import android.annotation.SuppressLint import android.location.Location import android.location.LocationListener import android.location.LocationManager @@ -67,7 +66,7 @@ class LocationFlow( private val locationManager: LocationManager, private val sdkInt: Int = Build.VERSION.SDK_INT, ) { - @SuppressLint("MissingPermission") + @Suppress("MissingPermission") fun get( minTimeMs: Long, minDistanceM: Float, @@ -114,7 +113,7 @@ class LocationFlow( } /** True when the registration was accepted; false when the provider refused it. */ - @SuppressLint("MissingPermission") + @Suppress("MissingPermission") private fun requestUpdates( provider: String, minTimeMs: Long, @@ -143,7 +142,7 @@ class LocationFlow( * the update request is — on a device where one refuses us, the others * should still seed. */ - @SuppressLint("MissingPermission") + @Suppress("MissingPermission") private fun freshestLastKnownLocation(providers: List): Location? = providers .mapNotNull { provider -> diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/logging/LogMonitor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/logging/LogMonitor.kt index 21fddb7c07..b9e4531bd5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/logging/LogMonitor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/logging/LogMonitor.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.service.logging -import android.annotation.SuppressLint import android.os.Handler import android.os.HandlerThread import android.os.Looper @@ -166,7 +165,7 @@ class StackSampler( companion object { const val SEPARATOR: String = "\r\n" - @SuppressLint("SimpleDateFormat") + @Suppress("SimpleDateFormat") val TIME_FORMATTER: SimpleDateFormat = SimpleDateFormat("MM-dd HH:mm:ss.SSS") } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/payments/PayToAppAvailability.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/payments/PayToAppAvailability.kt index 8657b94992..e52ed073da 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/payments/PayToAppAvailability.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/payments/PayToAppAvailability.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.service.payments -import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -196,7 +195,7 @@ object PayToAppAvailability { }.getOrNull() } - @SuppressLint("QueryPermissionsNeeded") + @Suppress("QueryPermissionsNeeded") private fun queryActivities( pm: PackageManager, intent: Intent, @@ -208,7 +207,7 @@ object PayToAppAvailability { }.getOrDefault(emptyList()) /** Packages that answer a URL nobody can own — i.e. general-purpose browsers. */ - @SuppressLint("QueryPermissionsNeeded") + @Suppress("QueryPermissionsNeeded") private fun browserPackages(pm: PackageManager): Set = queryActivities(pm, viewIntent(CONTROL_URL)).mapNotNull { it.packageName() }.toSet() /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/diskCache/VideoCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/diskCache/VideoCache.kt index 5f10d1a4fc..500e3a3db5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/diskCache/VideoCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/diskCache/VideoCache.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.service.playback.diskCache -import android.annotation.SuppressLint import android.content.Context import android.os.StatFs import androidx.media3.database.StandaloneDatabaseProvider @@ -34,7 +33,7 @@ import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCache.Companio import com.vitorpamplona.quartz.utils.Log import java.io.File -@SuppressLint("UnsafeOptInUsageError") +@Suppress("UnsafeOptInUsageError") class VideoCache { companion object { // Target fraction of currently-available disk space. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt index 8936bc37e8..e0b580ef74 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/nwc/NWCFinderFilterAssemblerSubscription.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc -import android.annotation.SuppressLint import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.vitorpamplona.amethyst.commons.model.Note @@ -30,7 +29,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourc import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip47WalletConnect.events.NwcRequestEvent -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun NWCFinderFilterAssemblerSubscription( note: Note, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt index e11e290113..fce50b38a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.navigation.drawer -import android.os.Build import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -34,6 +33,7 @@ import com.vitorpamplona.amethyst.commons.resources.drawer_section_navigate import com.vitorpamplona.amethyst.commons.resources.drawer_section_system import com.vitorpamplona.amethyst.commons.resources.drawer_section_you import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog +import com.vitorpamplona.amethyst.commons.ui.platform.supportsEmbeddedAppTabs import org.jetbrains.compose.resources.StringResource /** @@ -126,9 +126,8 @@ private val DrawerFeedsItems: List = NavBarItem.CALENDARS, NavBarItem.CALENDAR_COLLECTIONS, NavBarItem.SOFTWARE_APPS, - // Favorites can be pinned as inline tabs that render on a cross-process surface - // (SurfaceControlViewHost), which needs API 30+. Gate the whole grid on R+ for that reason. - NavBarItem.FAVORITE_APPS.takeIf { Build.VERSION.SDK_INT >= Build.VERSION_CODES.R }, + // Favorites can be pinned as inline tabs, which need a platform surface for them. + NavBarItem.FAVORITE_APPS.takeIf { supportsEmbeddedAppTabs }, NavBarItem.NAPPLETS, NavBarItem.NSITES, NavBarItem.FOLLOW_PACKS, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt index 41780eb4e3..0bdf70ceac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/navs/Nav.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.navigation.navs -import android.annotation.SuppressLint import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue import androidx.compose.runtime.Composable @@ -272,7 +271,7 @@ class Nav( } } - @SuppressLint("RestrictedApi") + @Suppress("RestrictedApi") override fun popUpTo( route: Route, klass: KClass, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollDeadlinePicker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollDeadlinePicker.kt index 3c98337d8e..7ec5a1187d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollDeadlinePicker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollDeadlinePicker.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.creators.polls -import android.annotation.SuppressLint import android.text.format.DateFormat import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -164,7 +163,7 @@ fun PollDeadlinePicker(model: ShortNotePostViewModel) { } } -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable fun PollDeadlinePickerPreview() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollOptionsField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollOptionsField.kt index 2a89c7ef1c..abb263f2dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollOptionsField.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/polls/PollOptionsField.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.creators.polls -import android.annotation.SuppressLint import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -154,7 +153,7 @@ fun PollTypeSelector( } } -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable fun NewPollClosingPreview() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollConsensusThreshold.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollConsensusThreshold.kt index d8dc0633d4..c05c747b13 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollConsensusThreshold.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollConsensusThreshold.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.creators.zappolls -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -104,7 +103,7 @@ fun ZapPollConsensusThreshold(pollViewModel: ShortNotePostViewModel) { } } -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable fun ZapPollConsensusThresholdPreview() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollDeadlinePicker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollDeadlinePicker.kt index d53f4c10e1..a961fd4d82 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollDeadlinePicker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollDeadlinePicker.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.creators.zappolls -import android.annotation.SuppressLint import android.text.format.DateFormat import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -164,7 +163,7 @@ fun ZapPollDeadlinePicker(model: ShortNotePostViewModel) { } } -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable fun ZapPollDeadlinePickerPreview() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollOption.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollOption.kt index 3bc9882b5e..aa66a0e2a4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollOption.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollOption.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.creators.zappolls -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Row import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.IconButton @@ -89,7 +88,7 @@ fun ZapPollOption( } } -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable fun ZapPollOptionPreview() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollVoteValueRange.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollVoteValueRange.kt index 884e372553..14787eedab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollVoteValueRange.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/zappolls/ZapPollVoteValueRange.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.creators.zappolls -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -120,7 +119,7 @@ fun ZapPollVoteValueRange(pollViewModel: ShortNotePostViewModel) { } } -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable fun ZapPollVoteValueRangePreview() { 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 cfecf51470..93fd11d3d5 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 @@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -46,6 +47,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.QrCodeScanne /** Android's [AppPlatform]: the app's own shell pieces, camera scanner, app launcher and icon caches. */ object AndroidAppPlatform : AppPlatform { + override val isCastingAvailable: Boolean get() = BuildConfig.IS_CASTING_AVAILABLE + @Composable override fun AppBottomBar( selectedRoute: Route?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt index 6ddf7c23a6..00080bf8d5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old -import android.annotation.SuppressLint import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box @@ -103,7 +102,7 @@ fun OldBookmarkListScreen( RenderOldBookmarkScreen(publicFeedViewModel, privateFeedViewModel, bookmarkState, accountViewModel, nav) } -@SuppressLint("LocalContextGetResourceValueCall") +@Suppress("LocalContextGetResourceValueCall") @Composable @OptIn(ExperimentalFoundationApi::class) private fun RenderOldBookmarkScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt index b235b71514..61fef65bf3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt @@ -91,6 +91,7 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_attest_remove import com.vitorpamplona.amethyst.commons.resources.buzz_attest_signed_title import com.vitorpamplona.amethyst.commons.resources.buzz_attest_topbar import com.vitorpamplona.amethyst.commons.resources.buzz_attest_warning +import com.vitorpamplona.amethyst.commons.search.calendar.SearchDate import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton @@ -110,10 +111,6 @@ import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonPrimitive -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale -import java.util.TimeZone // Common event kinds an agent might be restricted to — suggestions for the "Restrict to kind" field; // any 0–65535 is still accepted by free numeric entry. @@ -654,15 +651,18 @@ private fun parseOptionalUnix(input: String): OptionalUnix? { return OptionalUnix(parsed) } -private val UNIX_ECHO_FORMAT = - SimpleDateFormat("yyyy-MM-dd HH:mm 'UTC'", Locale.US).apply { timeZone = TimeZone.getTimeZone("UTC") } - -/** A readable UTC rendering of an entered epoch-seconds string, or null when it's blank/out of range. */ +/** A readable UTC rendering (`yyyy-MM-dd HH:mm UTC`) of an entered epoch-seconds string, or null when it's blank/out of range. */ private fun unixEcho(input: String): String? { if (input.isBlank()) return null val secs = input.toLongOrNull()?.takeIf { it in 0..4294967295L } ?: return null - return UNIX_ECHO_FORMAT.format(Date(secs * 1000)) + val day = SearchDate.civilFromDays(secs / SECONDS_PER_DAY) + val secondOfDay = secs % SECONDS_PER_DAY + val hours = (secondOfDay / 3600).toString().padStart(2, '0') + val minutes = (secondOfDay % 3600 / 60).toString().padStart(2, '0') + return "${day.ymd()} $hours:$minutes UTC" } +private const val SECONDS_PER_DAY = 86_400L + /** Serializes the `auth` tag to a JSON array string (values are hex / canonical ASCII). */ private fun OwnerAttestation.toTagJson(): String = toTag().joinToString(prefix = "[", postfix = "]") { "\"$it\"" } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt index bf21ff22b3..73e28e912e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt @@ -41,6 +41,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.subscribeAsFlow import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer +import com.vitorpamplona.quartz.utils.concurrent.ConcurrentSet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -50,7 +51,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import java.util.Collections /** * Backing ViewModel for the [AgentConsoleScreen] — the workspace owner's read-only @@ -94,7 +94,7 @@ class AgentConsoleViewModel : ViewModel() { private var watchJob: Job? = null /** Dedups ephemeral frames across relays and re-emissions (accessed off multiple readers). */ - private val observerSeen = Collections.synchronizedSet(HashSet()) + private val observerSeen = ConcurrentSet() /** Binds to [account] scoped to the community [relayUrl] — the console shows that community's fleet. */ fun bind( @@ -226,7 +226,7 @@ class AgentConsoleViewModel : ViewModel() { // Observer frames are ephemeral and unbounded over a long session; keep the // dedup set from growing without limit. We only render the newest ~200 rows, // so dropping the seen-history occasionally risks at most a stale duplicate. - if (observerSeen.size > MAX_OBSERVER_ROWS * 4) observerSeen.clear() + if (observerSeen.size() > MAX_OBSERVER_ROWS * 4) observerSeen.clear() val fresh = events .filterIsInstance() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt index 1a0c63157f..18052af7cf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt @@ -46,6 +46,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip29RelayGroups.GroupId import com.vitorpamplona.quartz.nipC7Chats.ChatEvent +import com.vitorpamplona.quartz.utils.concurrent.ConcurrentMap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -55,7 +56,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import java.util.concurrent.ConcurrentHashMap /** * Backing ViewModel for [BuzzDmListScreen] — the user's Buzz direct-message inbox. @@ -87,7 +87,7 @@ class BuzzDmListViewModel : ViewModel() { private var liveJob: Job? = null /** channelId -> relay it was discovered on (from the 44100 provenance). */ - private val memberChannels = ConcurrentHashMap() + private val memberChannels = ConcurrentMap() private val _rows = MutableStateFlow>(emptyList()) val rows: StateFlow> = _rows.asStateFlow() @@ -217,7 +217,9 @@ class BuzzDmListViewModel : ViewModel() { /** Fetch the NIP-29 directory (39000-39003) of every discovered channel so its `t`/roster load. */ private suspend fun fetchMetadata(account: Account) { val byRelay = - memberChannels.entries + memberChannels + .asMap() + .entries .groupBy({ it.value }, { it.key }) .mapValues { (_, ids) -> listOf(Filter(kinds = RELAY_GROUP_METADATA_KINDS, tags = mapOf("d" to ids))) } if (byRelay.isEmpty()) return @@ -233,7 +235,9 @@ class BuzzDmListViewModel : ViewModel() { val myPubkey = account.userProfile().pubkeyHex val hidden = BuzzDmRegistry.hiddenFor(myPubkey) val (hiddenDms, visibleDms) = - memberChannels.entries + memberChannels + .asMap() + .entries .mapNotNull { (channelId, relay) -> val channel = LocalCache.getOrCreateRelayGroupChannel(GroupId(channelId, relay)) val metadata = channel.event ?: return@mapNotNull null @@ -323,7 +327,8 @@ class BuzzDmListViewModel : ViewModel() { .filterValues { it in scoped } var changed = false memberships.forEach { (channelId, relay) -> - if (memberChannels.put(channelId, relay) == null) changed = true + if (memberChannels[channelId] == null) changed = true + memberChannels[channelId] = relay } // A kind-44101 takes the membership away: drop the row rather than leaving a // conversation the relay no longer lets us read. @@ -338,7 +343,7 @@ class BuzzDmListViewModel : ViewModel() { .let { BuzzChannelInvites.latestPerChannel(it) } .filterValues { it.removed } .keys - val gone = memberChannels.keys.filter { it in withdrawn } + val gone = memberChannels.asMap().keys.filter { it in withdrawn } if (gone.isNotEmpty()) { gone.forEach { memberChannels.remove(it) } changed = true diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt index c1f1ef0a50..e0d974d496 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup -import android.util.Log import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -143,6 +142,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.cordn.spec00Coordinator.JoinRequest import com.vitorpamplona.quartz.cordn.spec01GroupMetadata.CordnGroupMetadata import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt index e76d675bbf..a1c7a51c3f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord -import android.content.Intent import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -57,7 +56,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow @@ -103,6 +101,7 @@ import com.vitorpamplona.amethyst.commons.resources.copy_to_clipboard import com.vitorpamplona.amethyst.commons.resources.leave import com.vitorpamplona.amethyst.commons.resources.more_options import com.vitorpamplona.amethyst.commons.resources.quick_action_share +import com.vitorpamplona.amethyst.commons.ui.components.rememberTextSharer import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ShorterTopAppBar @@ -749,7 +748,7 @@ private fun InviteLinkDialog( val concordInviteTitleStr = stringRes(Res.string.concord_invite_title) val clipboard = LocalClipboard.current val scope = rememberCoroutineScope() - val context = LocalContext.current + val textSharer = rememberTextSharer() AlertDialog( onDismissRequest = onDismiss, title = { Text(stringRes(Res.string.concord_invite_title)) }, @@ -768,13 +767,7 @@ private fun InviteLinkDialog( }, confirmButton = { TextButton(onClick = { - val send = - Intent().apply { - action = Intent.ACTION_SEND - type = "text/plain" - putExtra(Intent.EXTRA_TEXT, link) - } - context.startActivity(Intent.createChooser(send, concordInviteTitleStr)) + textSharer.share(link, null, concordInviteTitleStr) onDismiss() }) { Text(stringRes(Res.string.quick_action_share)) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt index 9b03cc9e9f..103668476f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt @@ -74,12 +74,12 @@ import com.vitorpamplona.amethyst.commons.resources.copy_to_clipboard import com.vitorpamplona.amethyst.commons.resources.more_options 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.DateTimeStyle +import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.concord.cord05Invites.ConcordInviteListEntry import kotlinx.coroutines.launch -import java.text.DateFormat -import java.util.Date import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon /** What the screen is currently showing. The unreadable case is deliberately not "empty" — see below. */ @@ -258,7 +258,7 @@ private fun InviteLinkRow( // a long prefix, so they are useless as labels until well past where the row wraps. Text(link.token.take(8), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge) Text( - stringRes(Res.string.concord_invite_links_created, DateFormat.getDateInstance(DateFormat.MEDIUM).format(Date(link.createdAt * 1000))), + stringRes(Res.string.concord_invite_links_created, formatDateTime(link.createdAt * 1000, DateTimeStyle.MEDIUM, DateTimeStyle.NONE)), style = MaterialTheme.typography.bodySmall, ) Text(link.url, style = MaterialTheme.typography.bodySmall, maxLines = 1, overflow = TextOverflow.Ellipsis) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessRelaySettingsSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessRelaySettingsSheet.kt index 10e0639223..16af1d0b1f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessRelaySettingsSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessRelaySettingsSheet.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -225,7 +224,7 @@ fun ChessRelaySettingsSheet( } } -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun StatRow(chessViewModel: ChessViewModel) { Column(horizontalAlignment = Alignment.CenterHorizontally) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessViewModelFactory.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessViewModelFactory.kt index a785dc80d5..ab7d7a646e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessViewModelFactory.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chess/ChessViewModelFactory.kt @@ -22,10 +22,12 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chess 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.nip64Chess.ChessDismissedGamesStore import com.vitorpamplona.amethyst.commons.nip64Chess.ChessPollingDefaults import com.vitorpamplona.amethyst.commons.nip64Chess.ChessViewModel +import kotlin.reflect.KClass /** * Factory for the shared [ChessViewModel], wired with the Android adapters. @@ -34,8 +36,11 @@ class ChessViewModelFactory( private val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(ChessViewModel::class.java)) { + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T { + if (modelClass == ChessViewModel::class) { return ChessViewModel( userPubkey = account.userProfile().pubkeyHex, publisher = AndroidChessPublisher(account), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/SellProduct.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/SellProduct.kt index dae7daa668..11bc0c490b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/SellProduct.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/SellProduct.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -97,7 +96,7 @@ import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.quartz.nip99Classifieds.tags.ConditionTag import kotlinx.collections.immutable.toImmutableList -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable fun SellProductPreview() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/RemoteImeView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/RemoteImeView.kt index aed8de884d..4f746b18bc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/RemoteImeView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/RemoteImeView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.embed -import android.annotation.SuppressLint import android.content.Context import android.os.SystemClock import android.text.Editable @@ -52,7 +51,7 @@ import kotlinx.serialization.json.put * It covers the soft keyboard, hardware keyboards, autofill, paste, and context-menu edits uniformly, * because every one of them mutates the same [Editable] and we flush the resulting state. */ -@SuppressLint("ViewConstructor", "AppCompatCustomView") +@Suppress("ViewConstructor", "AppCompatCustomView") class RemoteImeView( context: Context, ) : EditText(context) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt index 7329448b51..5f500691c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/FollowPackFeedScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -106,7 +105,7 @@ fun FollowPackFeedScreen( } } -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun PrepareViewModelsFollowPackScreen( note: AddressableNote, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt index b9f214640e..b962a15678 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash -import android.annotation.SuppressLint import androidx.compose.foundation.layout.Column import androidx.compose.material3.IconButton import androidx.compose.material3.Text @@ -64,7 +63,7 @@ fun GeoHashScreen( PrepareViewModelsGeoHashScreen(tag, accountViewModel, nav) } -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun PrepareViewModelsGeoHashScreen( tag: Route.Geohash, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt index 18aa1cea55..0c9abc4a88 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.datasource -import android.annotation.SuppressLint import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.vitorpamplona.amethyst.commons.model.navigation.Route @@ -28,7 +27,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.geohash.GeohashQueryState import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun GeoHashFilterAssemblerSubscription( tag: Route.Geohash, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt index 9832596f06..8957b08403 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag -import android.annotation.SuppressLint import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -81,7 +80,7 @@ fun HashtagScreen( PrepareViewModelsHashtagScreen(tag, accountViewModel, nav) } -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun PrepareViewModelsHashtagScreen( tag: Route.Hashtag, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index 5823df6c8b..e73f02e3ce 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home -import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import androidx.activity.compose.BackHandler @@ -953,7 +952,7 @@ private fun BottomRowActions( } } -@SuppressLint("ViewModelConstructorInComposable") +@Suppress("ViewModelConstructorInComposable") @Preview @Composable private fun BottomRowActionsPreview() { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt index 6dcb4548ed..e929462e5e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.lists -import android.content.Intent import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column @@ -59,7 +58,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -87,6 +85,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.ClickableBox 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.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -433,30 +432,14 @@ private fun ListActionsMenuButton( } if (isActionListOpen.value) { - val context = LocalContext.current + val textSharer = rememberTextSharer() M3ActionDialog( title = stringRes(Res.string.list_actions_dialog_title), onDismiss = { isActionListOpen.value = false }, ) { M3ActionSection { M3ActionRow(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, - externalLinkForNote(note()), - ) - putExtra( - Intent.EXTRA_TITLE, - quickActionShareBrowserLinkStr, - ) - } - - val shareIntent = - Intent.createChooser(sendIntent, quickActionShareStr) - context.startActivity(shareIntent) + textSharer.share(externalLinkForNote(note()), quickActionShareBrowserLinkStr, quickActionShareStr) isActionListOpen.value = false } M3ActionRow(icon = MaterialSymbols.Edit, text = stringRes(Res.string.follow_set_edit_list_metadata)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt index 633661c400..2f91f2666d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.packs -import android.content.Intent import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Column @@ -52,7 +51,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp @@ -76,6 +74,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.ClickableBox 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.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -284,30 +283,14 @@ private fun ListActionsMenuButton( } if (isActionListOpen.value) { - val context = LocalContext.current + val textSharer = rememberTextSharer() M3ActionDialog( title = stringRes(Res.string.pack_actions_dialog_title), onDismiss = { isActionListOpen.value = false }, ) { M3ActionSection { M3ActionRow(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, - externalLinkForNote(note()), - ) - putExtra( - Intent.EXTRA_TITLE, - quickActionShareBrowserLinkStr, - ) - } - - val shareIntent = - Intent.createChooser(sendIntent, quickActionShareStr) - context.startActivity(shareIntent) + textSharer.share(externalLinkForNote(note()), quickActionShareBrowserLinkStr, quickActionShareStr) isActionListOpen.value = false } M3ActionRow(icon = MaterialSymbols.Edit, text = stringRes(Res.string.follow_pack_edit_list_metadata)) { 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 1763fa96a3..cf52314cdb 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 @@ -84,7 +84,15 @@ import com.vitorpamplona.amethyst.commons.napplet.ui.PolicyCard import com.vitorpamplona.amethyst.commons.napplet.ui.symbol import com.vitorpamplona.amethyst.commons.nip46RemoteSigner.ui.Nip46ActivityCard import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.browser_permission_allowed +import com.vitorpamplona.amethyst.commons.resources.browser_permission_ask +import com.vitorpamplona.amethyst.commons.resources.browser_permission_blocked +import com.vitorpamplona.amethyst.commons.resources.browser_permission_camera +import com.vitorpamplona.amethyst.commons.resources.browser_permission_location +import com.vitorpamplona.amethyst.commons.resources.browser_permission_microphone +import com.vitorpamplona.amethyst.commons.resources.browser_permission_section import com.vitorpamplona.amethyst.commons.resources.cancel +import com.vitorpamplona.amethyst.commons.resources.confirm import com.vitorpamplona.amethyst.commons.resources.napplet_connected_app_capabilities import com.vitorpamplona.amethyst.commons.resources.napplet_connected_app_forget import com.vitorpamplona.amethyst.commons.resources.napplet_connected_app_op_overrides @@ -106,6 +114,7 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_policy_paranoid_desc import com.vitorpamplona.amethyst.commons.resources.napplet_policy_reasonable import com.vitorpamplona.amethyst.commons.resources.napplet_policy_reasonable_desc import com.vitorpamplona.amethyst.commons.resources.napplet_signer_permissions_revoke_all +import com.vitorpamplona.amethyst.commons.resources.napplet_untitled import com.vitorpamplona.amethyst.commons.resources.nip46_signer_activity_title import com.vitorpamplona.amethyst.commons.resources.nip46_signer_app_relays_inbox import com.vitorpamplona.amethyst.commons.resources.nip46_signer_app_relays_own_hint @@ -116,6 +125,7 @@ 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.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.napplet.NappletBrokerService import com.vitorpamplona.amethyst.napplet.WebSitePermissionRegistry @@ -129,13 +139,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46.Nip46Reconne 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.amethyst.ui.stringRes import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import com.vitorpamplona.amethyst.commons.R as CommonsR +import org.jetbrains.compose.resources.StringResource private data class ConnectedAppDetailState( val title: String, @@ -154,7 +163,7 @@ fun ConnectedAppDetailScreen( ) { val capabilityLedger = Amethyst.instance.nappletPermissionLedger val signerLedger = remember { NostrSignerPermissionLedger(Amethyst.instance.signerPermissionStore) } - val untitled = stringRes(CommonsR.string.napplet_untitled) + val untitled = stringRes(Res.string.napplet_untitled) var state by remember { mutableStateOf(null) } var reload by remember { mutableIntStateOf(0) } @@ -557,7 +566,7 @@ private const val BROWSER_PREFIX = "browser:" private fun SitePermissionsSection(origin: String) { val all by WebSitePermissionRegistry.decisions.collectAsStateWithLifecycle() val decisions = all[origin].orEmpty() - SectionHeader(stringRes(CommonsR.string.browser_permission_section)) + SectionHeader(stringRes(Res.string.browser_permission_section)) Surface( color = MaterialTheme.colorScheme.surfaceVariant, shape = MaterialTheme.shapes.medium, @@ -575,9 +584,9 @@ private fun SitePermissionsSection(origin: String) { Text( stringRes( when (permission) { - BrowserSitePermission.CAMERA -> CommonsR.string.browser_permission_camera - BrowserSitePermission.MICROPHONE -> CommonsR.string.browser_permission_microphone - BrowserSitePermission.LOCATION -> CommonsR.string.browser_permission_location + BrowserSitePermission.CAMERA -> Res.string.browser_permission_camera + BrowserSitePermission.MICROPHONE -> Res.string.browser_permission_microphone + BrowserSitePermission.LOCATION -> Res.string.browser_permission_location }, ), style = MaterialTheme.typography.bodyMedium, @@ -603,11 +612,11 @@ private fun SitePermissionsSection(origin: String) { } } -private fun BrowserSitePermission.Decision.labelRes(): Int = +private fun BrowserSitePermission.Decision.labelRes(): StringResource = when (this) { - BrowserSitePermission.Decision.ASK -> CommonsR.string.browser_permission_ask - BrowserSitePermission.Decision.ALLOW -> CommonsR.string.browser_permission_allowed - BrowserSitePermission.Decision.BLOCK -> CommonsR.string.browser_permission_blocked + BrowserSitePermission.Decision.ASK -> Res.string.browser_permission_ask + BrowserSitePermission.Decision.ALLOW -> Res.string.browser_permission_allowed + BrowserSitePermission.Decision.BLOCK -> Res.string.browser_permission_blocked } @Composable @@ -800,7 +809,7 @@ private fun CapabilityPermissionDialog( TextButton( onClick = { onSetGrant(if (selected == GrantState.ASK) null else selected) }, ) { - Text(stringRes(android.R.string.ok)) + Text(stringRes(Res.string.confirm)) } }, dismissButton = { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt index 9a20f90a6d..0f42354ae0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt @@ -50,7 +50,6 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.navigation.navs.Nav import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -68,7 +67,7 @@ fun LoadRedirectScreen( eventId: String?, isPrivate: Boolean, accountViewModel: AccountViewModel, - nav: Nav, + nav: INav, ) { if (eventId == null) return diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/RelayFeedScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/RelayFeedScreen.kt index 2ebfd0745b..7951cff5e7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/RelayFeedScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/RelayFeedScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relay -import android.annotation.SuppressLint import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -59,7 +58,7 @@ fun RelayFeedScreen( PrepareViewModelsRelayFeedScreen(tag, normalizedUrl, accountViewModel, nav) } -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun PrepareViewModelsRelayFeedScreen( tag: Route.RelayFeed, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt index ff9348aea5..2cbe1eb4f2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings import androidx.compose.ui.platform.UriHandler -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.navigation.Route @@ -54,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.resources.event_sync_title import com.vitorpamplona.amethyst.commons.resources.favorite_dvms_search_keywords import com.vitorpamplona.amethyst.commons.resources.favorite_dvms_title import com.vitorpamplona.amethyst.commons.resources.home_tabs_search_keywords +import com.vitorpamplona.amethyst.commons.resources.ic_tor import com.vitorpamplona.amethyst.commons.resources.import_follows_search_keywords import com.vitorpamplona.amethyst.commons.resources.media_servers import com.vitorpamplona.amethyst.commons.resources.media_servers_search_keywords @@ -85,6 +85,7 @@ import com.vitorpamplona.amethyst.commons.resources.relay_auth_search_keywords import com.vitorpamplona.amethyst.commons.resources.relay_auth_settings_title import com.vitorpamplona.amethyst.commons.resources.relay_setup import com.vitorpamplona.amethyst.commons.resources.relay_setup_search_keywords +import com.vitorpamplona.amethyst.commons.resources.relays import com.vitorpamplona.amethyst.commons.resources.request_to_vanish import com.vitorpamplona.amethyst.commons.resources.request_to_vanish_search_keywords import com.vitorpamplona.amethyst.commons.resources.reset_marmot_search_keywords @@ -143,7 +144,7 @@ fun buildSettingsCatalog( listOf( SettingsEntry( titleRes = Res.string.relay_setup, - icon = SettingsIcon.Painter(R.drawable.relays, 4), + icon = SettingsIcon.Painter(Res.drawable.relays, 4), keywordsRes = Res.string.relay_setup_search_keywords, ) { nav.nav(Route.EditRelays) }, symEntry(Res.string.event_sync_title, MaterialSymbols.Sync, Res.string.event_sync_search_keywords, Route.EventSync), @@ -185,7 +186,7 @@ fun buildSettingsCatalog( listOf( SettingsEntry( titleRes = Res.string.privacy_options, - icon = SettingsIcon.Painter(R.drawable.ic_tor, 1), + icon = SettingsIcon.Painter(Res.drawable.ic_tor, 1), keywordsRes = Res.string.privacy_options_search_keywords, ) { nav.nav(Route.PrivacyOptions) }, symEntry(Res.string.ui_preferences, MaterialSymbols.Settings, Res.string.ui_preferences_search_keywords, Route.Settings), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsSectionCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsSectionCard.kt index 92d2e53f19..748091b5b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsSectionCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsSectionCard.kt @@ -51,8 +51,9 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols +import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes -import com.vitorpamplona.amethyst.ui.painterRes +import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.StringResource import androidx.compose.material3.Icon as Material3Icon @@ -130,7 +131,7 @@ internal fun SettingsItem( @Composable internal fun SettingsItem( title: StringResource, - iconPainter: Int, + iconPainter: DrawableResource, iconPainterRef: Int, isDanger: Boolean = false, trailing: @Composable () -> Unit = {}, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt index 74fa8e87ba..da2b8a8563 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt @@ -96,23 +96,23 @@ open class StringFeedViewModel( } init { - Log.d("Init", this.javaClass.simpleName) + Log.d("Init") { "${this::class.simpleName}" } viewModelScope.launch(Dispatchers.IO) { LocalCache.live.newEventBundles.collect { newNotes -> - Log.d("Rendering Metrics") { "Update feeds: ${this@StringFeedViewModel.javaClass.simpleName} with ${newNotes.size}" } + Log.d("Rendering Metrics") { "Update feeds: ${this@StringFeedViewModel::class.simpleName} with ${newNotes.size}" } invalidateData() } } viewModelScope.launch(Dispatchers.IO) { LocalCache.live.deletedEventBundles.collect { newNotes -> - Log.d("Rendering Metrics") { "Delete feeds: ${this@StringFeedViewModel.javaClass.simpleName} with ${newNotes.size}" } + Log.d("Rendering Metrics") { "Delete feeds: ${this@StringFeedViewModel::class.simpleName} with ${newNotes.size}" } invalidateData() } } } override fun onCleared() { - Log.d("Init") { "OnCleared: ${this.javaClass.simpleName}" } + Log.d("Init") { "OnCleared: ${this::class.simpleName}" } bundler.cancel() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt index 918d6e76bc..1361e34757 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt @@ -58,7 +58,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.VideoButtonLocation @@ -89,6 +88,7 @@ import com.vitorpamplona.amethyst.commons.resources.video_player_settings_reorde 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.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size20dp import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow @@ -125,8 +125,9 @@ fun VideoPlayerSettingsScreen( @Composable fun VideoPlayerSettingsContent(accountViewModel: AccountViewModel) { val buttonItems by accountViewModel.videoPlayerButtonItemsFlow().collectAsStateWithLifecycle() + val isCastingAvailable = LocalAppPlatform.current.isCastingAvailable val displayedItems = - if (BuildConfig.IS_CASTING_AVAILABLE) { + if (isCastingAvailable) { buttonItems } else { buttonItems.filter { it.action != VideoPlayerAction.Cast } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt index f572ce2a48..c0debbb316 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt @@ -22,8 +22,10 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.dal import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel +import kotlin.reflect.KClass class HiddenAccountsFeedViewModel( val account: Account, @@ -31,12 +33,15 @@ class HiddenAccountsFeedViewModel( class Factory( val account: Account, ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(HiddenAccountsFeedViewModel::class.java)) { + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T { + if (modelClass == HiddenAccountsFeedViewModel::class) { @Suppress("UNCHECKED_CAST") return HiddenAccountsFeedViewModel(account) as T } - throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}") + throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.simpleName}") } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index 3a463ac70e..e22773d3c0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview -import android.annotation.SuppressLint import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable @@ -1286,7 +1285,7 @@ private fun RenderApprovalIfNeeded( } } -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable private fun RenderClassifiedsReaderForThread( noteEvent: ClassifiedsEvent, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlScreen.kt index b6c5d6bb07..0e42872c12 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.url -import android.annotation.SuppressLint import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -59,7 +58,7 @@ fun UrlScreen( PrepareViewModelsUrlScreen(url, accountViewModel, nav) } -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun PrepareViewModelsUrlScreen( url: String, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/datasource/UrlFilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/datasource/UrlFilterAssemblerSubscription.kt index 03aa1ae197..4864613b42 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/datasource/UrlFilterAssemblerSubscription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/datasource/UrlFilterAssemblerSubscription.kt @@ -20,14 +20,13 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.url.datasource -import android.annotation.SuppressLint import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription import com.vitorpamplona.amethyst.commons.relayClient.url.UrlQueryState import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -@SuppressLint("StateFlowValueCalledInComposition") +@Suppress("StateFlowValueCalledInComposition") @Composable fun UrlFilterAssemblerSubscription( url: String, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt index c2394ff37e..a32fc0781c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt @@ -159,9 +159,11 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_sats import com.vitorpamplona.amethyst.commons.ui.components.util.getText 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.DateTimeStyle import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuMeltFlowState import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuMintFlowState @@ -170,6 +172,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuRedeemF import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuSendTokenFlowState import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip60Cashu.history.CashuSpendingHistoryEvent @@ -180,9 +183,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch -import java.text.DateFormat -import java.text.NumberFormat -import java.util.Date import androidx.compose.material3.Icon as Material3Icon @OptIn(ExperimentalMaterial3Api::class) @@ -882,7 +882,7 @@ private fun EvacuateMintDialog( private fun BalanceCard(balanceSats: Long) { val formatted = remember(balanceSats) { - NumberFormat.getIntegerInstance().format(balanceSats) + formatGrouped(balanceSats) } Card( modifier = Modifier.fillMaxWidth(), @@ -990,7 +990,7 @@ private fun MintRow( ) { val formattedBalance = remember(balanceSats) { - NumberFormat.getIntegerInstance().format(balanceSats) + formatGrouped(balanceSats) } Card( modifier = Modifier.fillMaxWidth(), @@ -1151,9 +1151,7 @@ private fun HistoryRow( } Text( text = - DateFormat - .getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT) - .format(Date(entry.createdAt * 1000)), + formatDateTime(entry.createdAt * 1000, DateTimeStyle.MEDIUM, DateTimeStyle.SHORT), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant, ) @@ -1163,7 +1161,7 @@ private fun HistoryRow( val amount = decoded?.amount if (amount != null) { Text( - text = (if (isIncoming) "+" else "−") + NumberFormat.getIntegerInstance().format(amount), + text = (if (isIncoming) "+" else "−") + formatGrouped(amount), style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.SemiBold, color = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt index 497f0ac69d..be7ca8faed 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.clink.ClinkDebitWalletEntryNorm import com.vitorpamplona.amethyst.commons.model.nip47WalletConnect.NwcSignerState @@ -132,11 +131,7 @@ private const val NWC_TIMEOUT_MS = 30_000L class WalletViewModel : ViewModel() { // Error text is resolved here rather than in the screens because a single _error - // flow feeds several of them. WalletViewModel only ever runs in the main process, - // so Amethyst.instance is always initialised (see AccountViewModel for the same - // pattern); the :napplet process never builds a wallet screen. - private val appContext get() = Amethyst.instance.appContext - + // flow feeds several of them. private suspend fun text(id: StringResource) = loadStringRes(id) private suspend fun text( @@ -372,12 +367,13 @@ class WalletViewModel : ViewModel() { ) { val acc = account ?: return val pointer = _debitWallets.value.firstOrNull { it.id == walletId }?.pointer ?: return + val moneyOpRelays = accountViewModel?.host?.moneyOpRelays ?: return viewModelScope.launch { // A malformed budget (e.g. an out-of-spec frequency unit) makes requestBudget throw; // treat it as "no response" so the dialog dismisses instead of hanging on a spinner. val response = try { - ClinkDebitPayer.requestBudget(acc, Amethyst.instance.torEvaluatorFlow, pointer, amountSats, frequency) + ClinkDebitPayer.requestBudget(acc, moneyOpRelays, pointer, amountSats, frequency) } catch (_: IllegalArgumentException) { null } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.kt new file mode 100644 index 0000000000..dc617f48ed --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.kt @@ -0,0 +1,27 @@ +/* + * 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.model.chats + +/** + * Formats a paging reach point (epoch seconds) to a short, locale-aware month-year label, e.g. + * "Jun 2026", for the shared [com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard]. + */ +expect fun formatHistoryReachDate(epochSeconds: Long): String diff --git a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.ios.kt new file mode 100644 index 0000000000..63f4f1f0c8 --- /dev/null +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.ios.kt @@ -0,0 +1,32 @@ +/* + * 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.model.chats + +import platform.Foundation.NSDate +import platform.Foundation.NSDateFormatter +import platform.Foundation.dateWithTimeIntervalSince1970 + +// A formatter per call: this runs once per history-card render, and NSDateFormatter is not +// safe to share across threads. +actual fun formatHistoryReachDate(epochSeconds: Long): String = + NSDateFormatter() + .apply { setLocalizedDateFormatFromTemplate("MMMyyyy") } + .stringFromDate(NSDate.dateWithTimeIntervalSince1970(epochSeconds.toDouble())) diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.jvmAndroid.kt similarity index 74% rename from commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.kt rename to commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.jvmAndroid.kt index cf3fd25fb0..a18ee86748 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.kt +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/model/chats/HistoryDateFormat.jvmAndroid.kt @@ -24,9 +24,4 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -/** - * The Android locale date formatter for the shared [com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard] - * — passed in so the shared (KMP) card carries no `java.time` dependency. Formats a paging reach point - * (epoch seconds) to a short month-year label, e.g. "Jun 2026". - */ -fun formatHistoryReachDate(epochSeconds: Long): String = SimpleDateFormat("MMM yyyy", Locale.getDefault()).format(Date(epochSeconds * 1000)) +actual fun formatHistoryReachDate(epochSeconds: Long): String = SimpleDateFormat("MMM yyyy", Locale.getDefault()).format(Date(epochSeconds * 1000)) diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.android.kt new file mode 100644 index 0000000000..7b89d6d384 --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.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.ui.platform + +import android.os.Build + +actual val supportsEmbeddedAppTabs: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml index 04871363cd..d5c8c1f402 100644 --- a/commonsUI/src/commonMain/composeResources/values/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values/strings.xml @@ -6245,4 +6245,11 @@ Stop and send Search or enter address Untitled nApplet + Site permissions + Use your camera + Use your microphone + Know your approximate location + Ask + Allowed + Blocked 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 fb0e9ad03b..99b7ebdf33 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 @@ -39,6 +39,9 @@ import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer */ @Stable interface AppPlatform { + /** Whether this build can cast media to a TV (the Play flavour's Cast SDK). */ + val isCastingAvailable: Boolean get() = false + @Composable fun AppBottomBar( selectedRoute: Route?, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.kt new file mode 100644 index 0000000000..031114b884 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.kt @@ -0,0 +1,27 @@ +/* + * 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.ui.platform + +/** + * Whether favorite apps can be pinned as inline tabs. On Android they render on a cross-process + * surface (SurfaceControlViewHost), which needs API 30; other front ends have no such surface yet. + */ +expect val supportsEmbeddedAppTabs: Boolean diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalog.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalog.kt index 5260295216..743b2ff564 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalog.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalog.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.commons.ui.settings import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol +import org.jetbrains.compose.resources.DrawableResource import org.jetbrains.compose.resources.StringResource /** Leading-icon representation mirroring the two [SettingsItem] overloads. */ @@ -30,7 +31,7 @@ sealed interface SettingsIcon { ) : SettingsIcon data class Painter( - val iconPainter: Int, + val iconPainter: DrawableResource, val iconPainterRef: Int, ) : SettingsIcon } diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.ios.kt new file mode 100644 index 0000000000..e5cd71284d --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.ios.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.ui.platform + +actual val supportsEmbeddedAppTabs: Boolean = false diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.jvm.kt new file mode 100644 index 0000000000..e5cd71284d --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/platform/EmbeddedAppSurfaces.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.ui.platform + +actual val supportsEmbeddedAppTabs: Boolean = false diff --git a/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalogFilterTest.kt b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalogFilterTest.kt index 10b234ad70..cb2c57b5dc 100644 --- a/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalogFilterTest.kt +++ b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/settings/SettingsCatalogFilterTest.kt @@ -59,7 +59,7 @@ class SettingsCatalogFilterTest { isDanger: Boolean = false, ) = SettingsEntry( titleRes = titleRes, - icon = SettingsIcon.Painter(0, 0), + icon = SettingsIcon.Painter(Res.drawable.relays, 0), keywordsRes = keywordsRes, isDanger = isDanger, onClick = {}, diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.kt index d00545acf6..25636205b5 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.kt @@ -38,6 +38,8 @@ expect class ConcurrentSet() { fun size(): Int + fun clear() + /** A point-in-time copy — safe to iterate or diff against without a lock. */ fun snapshot(): Set } diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.jvmAndroid.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.jvmAndroid.kt index 94a0754c11..f0ff0d18c7 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.jvmAndroid.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.jvmAndroid.kt @@ -31,5 +31,7 @@ actual class ConcurrentSet { actual fun size(): Int = set.size + actual fun clear() = set.clear() + actual fun snapshot(): Set = HashSet(set) } diff --git a/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.native.kt b/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.native.kt index 70bf86f133..5055decd93 100644 --- a/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.native.kt +++ b/quartz/src/nativeMain/kotlin/com/vitorpamplona/quartz/utils/concurrent/ConcurrentSet.native.kt @@ -42,5 +42,7 @@ actual class ConcurrentSet { actual fun size(): Int = ref.load().size + actual fun clear() = ref.store(HashSet()) + actual fun snapshot(): Set = HashSet(ref.load()) } From fc212b79f88cf12116c9fa0db83a2b972eadeb97 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 04:38:04 +0000 Subject: [PATCH 05/20] refactor: move 28 more screens into commonsUI (step 6 wave 4) The wave-4 seams leave these screens and their closures (66 files) without an Android-only dependency: the hashtag, relay and geohash feeds, people lists and follow packs, the hidden/blocked/muted/spam settings, the Cordn hub/key-package/link/coordinator screens, relay auth, privacy, home tabs, security filters, profile UI, drawer and video-player settings, the Cashu wallet with NWC and CLINK setup, the Concord channel list and invite links, the agent attestation screen and the redirect loader. Seven more feed-view-model factories override the multiplatform create(KClass, CreationExtras); RelayAuthSettingsScreen fills its templates with replace instead of String.format; helpers the app still calls lose `internal`. The plan doc records waves 3 and 4. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../components/SelectNotificationProvider.kt | 2 +- .../compose/RelayAuthPromptHost.kt | 1 + .../amethyst/ui/actions/NewMediaView.kt | 2 +- .../mediaServers/FileServerSelectionRow.kt | 2 +- .../amethyst/ui/navigation/AppNavigation.kt | 56 +++++++++---------- .../ui/navigation/drawer/DrawerContent.kt | 3 + .../amethyst/ui/screen/UserFeedView.kt | 1 + .../loggedIn/badges/post/NewBadgeDialog.kt | 2 +- .../loggedIn/buzz/AgentConsoleScreen.kt | 1 + .../CalendarReminderSettingsScreen.kt | 8 +-- .../chats/rooms/ChatroomHeaderCompose.kt | 2 +- .../chats/utils/ChatFileUploadDialog.kt | 2 +- .../nip23LongForm/LongFormPostScreen.kt | 2 +- .../dal/FollowPackMembersUserFeedViewModel.kt | 2 +- .../ui/screen/loggedIn/home/HomeScreen.kt | 4 +- .../loggedIn/home/ShortNotePostScreen.kt | 2 +- .../profile/hashtags/TabFollowedTags.kt | 2 +- .../relays/local/LocalRelayListView.kt | 2 +- .../loggedIn/settings/AllSettingsScreen.kt | 3 + .../loggedIn/settings/AppSettingsScreen.kt | 5 ++ .../settings/ComposeSettingsScreen.kt | 6 ++ .../loggedIn/settings/NIP47SetupScreen.kt | 2 +- .../settings/NotificationSettingsScreen.kt | 4 ++ .../loggedIn/settings/ResourceUsageScreen.kt | 2 + .../loggedIn/settings/StringFeedView.kt | 1 + .../loggedIn/settings/UserSettingsScreen.kt | 1 + .../settings/cordn/CordnBackupScreen.kt | 2 +- .../settings/cordn/CordnMigrateScreen.kt | 2 +- .../wallet/OnchainTransactionsScreen.kt | 1 + .../wallet/OnchainTransactionsViewModel.kt | 1 + .../loggedIn/wallet/WalletDetailScreen.kt | 1 + .../loggedIn/wallet/WalletReceiveScreen.kt | 2 + .../ui/screen/loggedIn/wallet/WalletScreen.kt | 6 +- .../loggedIn/wallet/WalletSendScreen.kt | 2 + .../wallet/WalletTransactionsScreen.kt | 2 + .../amethyst/ui/tor/TorSettingsDialog.kt | 2 +- .../navigation/DrawerItemVisibilityTest.kt | 6 +- .../amethyst/navigation/DrawerSectionsTest.kt | 4 +- .../2026-09-27-one-ui-android-desktop.md | 41 ++++++++++++-- .../compose/RelayAuthComposeHelpers.kt | 4 +- .../drawer/DrawerSectionVisibility.kt | 2 +- .../ui/navigation/drawer/DrawerSections.kt | 2 +- .../commons}/ui/screen/UserFeedViewModel.kt | 3 +- .../loggedIn/buzz/AgentAttestationScreen.kt | 3 +- .../chats/cordnGroup/CordnGroupInfoScreen.kt | 2 +- .../concord/ConcordChannelListScreen.kt | 4 +- .../concord/ConcordInviteLinksScreen.kt | 2 +- .../geocaches/dal/GeocachesHereFeedFilter.kt | 2 +- .../dal/GeocachesHereFeedViewModel.kt | 9 ++- .../screen/loggedIn/geohash/GeoHashScreen.kt | 10 ++-- .../loggedIn/geohash/GeocachesHereChip.kt | 8 +-- .../loggedIn/geohash/NewGeoNoteButton.kt | 2 +- .../loggedIn/geohash/dal/GeoHashFeedFilter.kt | 2 +- .../geohash/dal/GeoHashFeedViewModel.kt | 9 ++- .../GeoHashFilterAssemblerSubscription.kt | 2 +- .../loggedIn/hashtag/HashtagFeedLoaded.kt | 2 +- .../screen/loggedIn/hashtag/HashtagScreen.kt | 10 ++-- .../loggedIn/hashtag/NewHashtagNoteButton.kt | 2 +- .../loggedIn/hashtag/dal/HashtagFeedFilter.kt | 2 +- .../hashtag/dal/HashtagFeedViewModel.kt | 9 ++- .../HashtagFilterAssemblerSubscription.kt | 2 +- .../loggedIn/lists/display/PeopleListView.kt | 2 +- .../lists/display/ShowUserSuggestions.kt | 3 +- .../lists/display/lists/PeopleListScreen.kt | 12 ++-- .../display/lists/PeopleListViewModel.kt | 3 +- .../lists/display/packs/FollowPackScreen.kt | 12 ++-- .../display/packs/FollowPackViewModel.kt | 3 +- .../loggedIn/privacy/PrivacyOptionsScreen.kt | 14 ++--- .../loggedIn/redirect/LoadRedirectScreen.kt | 3 +- .../loggedIn/relay/NewRelayNoteButton.kt | 2 +- .../screen/loggedIn/relay/RelayFeedScreen.kt | 10 ++-- .../loggedIn/relay/dal/RelayFeedFilter.kt | 2 +- .../relay/dal/SingleRelayFeedViewModel.kt | 9 ++- .../RelayFeedFilterAssemblerSubscription.kt | 2 +- .../relayauth/RelayAuthSettingsScreen.kt | 17 +++--- .../loggedIn/settings/BlockedUsersScreen.kt | 8 +-- .../loggedIn/settings/DrawerSettingsScreen.kt | 8 +-- .../loggedIn/settings/DrawerSettingsState.kt | 6 +- .../loggedIn/settings/HiddenWordsScreen.kt | 8 +-- .../settings/HomeTabsSettingsScreen.kt | 2 +- .../loggedIn/settings/MutedThreadsScreen.kt | 8 +-- .../settings/ProfileUiSettingsScreen.kt | 2 +- .../settings/SecurityFiltersScreen.kt | 2 +- .../loggedIn/settings/SecurityListsCommon.kt | 6 +- .../screen/loggedIn/settings/SettingsRows.kt | 2 +- .../loggedIn/settings/SettingsSectionCard.kt | 22 ++++---- .../loggedIn/settings/SpammingUsersScreen.kt | 8 +-- .../loggedIn/settings/StringFeedViewModel.kt | 3 +- .../settings/VideoPlayerSettingsScreen.kt | 2 +- .../settings/cordn/CordnCoordinatorsScreen.kt | 6 +- .../loggedIn/settings/cordn/CordnHubScreen.kt | 8 +-- .../settings/cordn/CordnKeyPackagesScreen.kt | 4 +- .../settings/cordn/CordnLinkScreen.kt | 4 +- .../settings/dal/HiddenAccountsFeedFilter.kt | 2 +- .../dal/HiddenAccountsFeedViewModel.kt | 4 +- .../settings/dal/HiddenWordsFeedFilter.kt | 2 +- .../settings/dal/HiddenWordsFeedViewModel.kt | 11 +++- .../settings/dal/MutedThreadsFeedFilter.kt | 2 +- .../settings/dal/MutedThreadsFeedViewModel.kt | 9 ++- .../settings/dal/SpammerAccountsFeedFilter.kt | 2 +- .../dal/SpammerAccountsFeedViewModel.kt | 11 +++- .../wallet/AddClinkDebitWalletScreen.kt | 6 +- .../loggedIn/wallet/AddNwcWalletScreen.kt | 6 +- .../loggedIn/wallet/CashuWalletScreen.kt | 6 +- .../screen/loggedIn/wallet/WalletViewModel.kt | 3 +- 105 files changed, 333 insertions(+), 219 deletions(-) rename {amethyst/src/main/java/com/vitorpamplona/amethyst/service => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/navigation/drawer/DrawerSectionVisibility.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/navigation/drawer/DrawerSections.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/UserFeedViewModel.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedFilter.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedViewModel.kt (85%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geohash/GeoHashScreen.kt (94%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geohash/GeocachesHereChip.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geohash/NewGeoNoteButton.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geohash/dal/GeoHashFeedFilter.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geohash/dal/GeoHashFeedViewModel.kt (85%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/hashtag/HashtagFeedLoaded.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/hashtag/HashtagScreen.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/hashtag/NewHashtagNoteButton.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/hashtag/dal/HashtagFeedFilter.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/hashtag/dal/HashtagFeedViewModel.kt (85%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/lists/display/PeopleListView.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/lists/display/ShowUserSuggestions.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/lists/display/lists/PeopleListViewModel.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/lists/display/packs/FollowPackViewModel.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relay/NewRelayNoteButton.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relay/RelayFeedScreen.kt (93%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relay/dal/RelayFeedFilter.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relay/dal/SingleRelayFeedViewModel.kt (84%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relay/datasource/RelayFeedFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/BlockedUsersScreen.kt (89%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/DrawerSettingsState.kt (93%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/HiddenWordsScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/HomeTabsSettingsScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/MutedThreadsScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/ProfileUiSettingsScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/SecurityListsCommon.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/SettingsRows.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/SettingsSectionCard.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/SpammingUsersScreen.kt (89%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/StringFeedViewModel.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/cordn/CordnHubScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedFilter.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt (93%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/HiddenWordsFeedFilter.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/HiddenWordsFeedViewModel.kt (78%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/MutedThreadsFeedFilter.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/MutedThreadsFeedViewModel.kt (83%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedFilter.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedViewModel.kt (79%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/CashuWalletScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/WalletViewModel.kt (99%) diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt index 114e9c5ee7..bc5aab376e 100644 --- a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt +++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/SelectNotificationProvider.kt @@ -71,9 +71,9 @@ import com.vitorpamplona.amethyst.commons.resources.select_push_server import com.vitorpamplona.amethyst.commons.ui.components.SpinnerSelectionDialog import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsBlockTile import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.service.notifications.PushDistributorHandler -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsBlockTile import com.vitorpamplona.quartz.utils.Log import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt index 08759214ca..f6ec0e03b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthPromptHost.kt @@ -67,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.nip11RelayInfo.loadRelayInfo import com.vitorpamplona.amethyst.commons.relayClient.auth.RelayAuthPrompt import com.vitorpamplona.amethyst.commons.relayClient.auth.UserAuthChoice +import com.vitorpamplona.amethyst.commons.relayClient.authCommand.compose.LoadRelayAuthUser import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.channel.observeChannel import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserInfo import com.vitorpamplona.amethyst.commons.relayauth.AuthPurpose diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 9f534be04a..abe7354538 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -78,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.SettingSwitchItem +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText @@ -85,7 +86,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/FileServerSelectionRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/FileServerSelectionRow.kt index e9cffad67d..cc37db7d72 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/FileServerSelectionRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/FileServerSelectionRow.kt @@ -28,7 +28,7 @@ import com.vitorpamplona.amethyst.commons.resources.file_server import com.vitorpamplona.amethyst.commons.resources.file_server_description import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import kotlinx.collections.immutable.toImmutableList @Composable 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 45a6bc0fc9..a3ad111652 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 @@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.memb import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.podcasts.BookmarkedPodcastsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.repositories.BookmarkedRepositoriesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.browser.BrowserScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentAttestationScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentPersonaEditScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentWorkBoardScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzForumPostScreen @@ -80,9 +81,12 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzInviteScre import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzNewDmScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.JobBoardScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.WorkflowRunBoardScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnGroupInfoScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnInvitationsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.geohashChat.GeohashTeleportScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordChannelListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordHomeScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordInviteLinksScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordInviteScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupBrowseScreen @@ -99,11 +103,15 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membersh import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.FavoriteAppsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.GeocacheHuntScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.NewGeocacheHuntScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.GeoHashScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.gitRepositories.GitRepositoriesScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.HashtagScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.highlights.HighlightsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.interestSets.display.InterestSetScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.interestSets.list.ListOfInterestSetsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.interestSets.list.metadata.InterestSetMetadataScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.lists.PeopleListScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.packs.FollowPackScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.list.ListOfPeopleListsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.MusicPlaylistsScreen @@ -118,21 +126,41 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.PodcastsSc import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.PodcastAuthoringScreen 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.publicChats.PublicChatsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ShowQRScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.redirect.LoadRedirectScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay.RelayFeedScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relayauth.RelayAuthSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.RelayInformationScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.eventsync.EventSyncScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.nip43.RelayMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.vanish.VanishEventsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BlockedUsersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CallSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.DrawerSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.HiddenWordsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.HomeTabsSettingsScreen 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.ProfileUiSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.ReactionsSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SecurityFiltersScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SpammingUsersScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.VideoPlayerSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CordnCoordinatorsScreen +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.softwareapps.SoftwareAppDetailScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.SoftwareAppsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.AddClinkDebitWalletScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.AddNwcWalletScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.AddWalletScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuMintRecommendationsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuMintsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.ReloadMintScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TopUpMintScreen @@ -176,7 +204,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.display.Book import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.metadata.BookmarkGroupMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.OldBookmarkListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.WebAppScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.AgentAttestationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.AgentConsoleScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzCanvasScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzDmListScreen @@ -190,7 +217,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.detail.CalendarEv import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnCreateGroupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnCreateMembersScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnGroupChatScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnGroupInfoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CordnGroupListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.geohashChat.GeohashChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.geohashChat.GeohashChatsScreen @@ -204,11 +230,9 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.minichat.MinichatScre import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomByAuthorScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.NewGroupDMScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordChannelListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordChannelScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordCreateScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordEditScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordInviteLinksScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.EphemeralChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.metadata.NewEphemeralChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.PublicChatChannelScreen @@ -245,22 +269,18 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.create.NewGeocach import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail.GeocacheDetailScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.log.LogGeocacheFindScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashPostScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitNewIssueScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryCodeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryIssuesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryPullsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagPostScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.highlights.NewHighlightScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.HomeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.VoiceReplyScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.nip75Goals.NewGoalScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.AccountBackupScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.lists.PeopleListScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.packs.FollowPackScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.FollowPackMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleListMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.LiveStreamsScreen @@ -280,14 +300,10 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.EditPodc import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastEpisodeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastTrailerScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.PollPostScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.ProductsScreen 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.redirect.LoadRedirectScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relay.RelayFeedScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relayauth.RelayAuthSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.AllRelayListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip86.RelayManagementScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.subscriptions.ActiveSubscriptionsScreen @@ -296,29 +312,16 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.scheduledposts.ScheduledPos import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AllSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AudioVisualizerSettingsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.BlockedUsersScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ComposeSettingsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.DrawerSettingsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.HiddenWordsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.HomeTabsSettingsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.MutedThreadsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen 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.ProfileUiSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ResourceUsageScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SpammingUsersScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.UpdateZapAmountScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.UserSettingsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.VideoPlayerSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnBackupScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnCoordinatorsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnHubScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnKeyPackagesScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn.CordnLinkScreen 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.settings.nip46.Nip46SignerScreen @@ -328,9 +331,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.VideoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.hls.NewHlsVideoScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.AddClinkDebitWalletScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.AddNwcWalletScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.CashuWalletScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainTransactionsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletDetailScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletReceiveScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt index b31e3de9a6..e947d9f041 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerContent.kt @@ -129,6 +129,9 @@ import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncIma import com.vitorpamplona.amethyst.commons.ui.layouts.PermanentDrawerWidth import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarItemDef +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSection +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSectionVisibility +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSections 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.routes.routeFor diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index 75fe22c6e7..d680149ee3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.StickToTopOnPrepend import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.UserCompose +import com.vitorpamplona.amethyst.commons.ui.screen.UserFeedViewModel import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt index dbb9476edb..7fc22bdedd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt @@ -89,6 +89,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.SettingSwitchItem +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText @@ -96,7 +97,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt index cb4940267a..7b0b5b9711 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt @@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.commons.model.buzz.TokenTotals import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentAttestationScreen import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/CalendarReminderSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/CalendarReminderSettingsScreen.kt index 1115110ab4..d58c2154af 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/CalendarReminderSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/CalendarReminderSettingsScreen.kt @@ -54,13 +54,13 @@ import com.vitorpamplona.amethyst.commons.resources.settings_section_reminders import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsBlockTile +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSwitchTile import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.service.calendar.CalendarReminderWorker import com.vitorpamplona.amethyst.service.calendar.calendarReminderSettings -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsBlockTile -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsDivider -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSwitchTile import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt index 90e207fcef..6548036eb0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/ChatroomHeaderCompose.kt @@ -136,6 +136,7 @@ import com.vitorpamplona.amethyst.commons.ui.platform.rememberConcordImageModel import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.feed.types.observeUserNameByHex import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.privateDM.header.RoomNameDisplay +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordLeaveDialog import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.coordinatorDisplayName import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.AccountPictureModifier @@ -154,7 +155,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.marmotGro import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.marmotOtherMembers import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.rememberMarmotGroupAvatarUrl import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.header.reportWarningContentDescription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordLeaveDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.LoadEphemeralChatChannel import com.vitorpamplona.quartz.buzz.notifications.MemberAddedNotificationEvent import com.vitorpamplona.quartz.cordn.appEncryptedMedia.CordnMediaTag diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt index 8b52378ae7..12e4df74a7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt @@ -80,13 +80,13 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ShorterTopAppBar import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TitleIconModifier import com.vitorpamplona.amethyst.commons.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.SettingSwitchItem +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt index 9759574d1a..c0932f7e1b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt @@ -116,6 +116,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowU import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.AddZapraiserButton import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.ZapRaiserRequest import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.ForwardZapToButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.text.MentionPreservingInputTransformation import com.vitorpamplona.amethyst.commons.ui.text.onUiThread @@ -141,7 +142,6 @@ import com.vitorpamplona.amethyst.ui.note.creators.location.GeoHashPostSection import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ObserveInboxRelayListAndDisplayIfNotFound -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.quartz.nip01Core.core.HexKey import kotlinx.collections.immutable.persistentListOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/dal/FollowPackMembersUserFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/dal/FollowPackMembersUserFeedViewModel.kt index 83b6356ff3..391d510789 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/dal/FollowPackMembersUserFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/feed/dal/FollowPackMembersUserFeedViewModel.kt @@ -24,7 +24,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.AddressableNote -import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.UserFeedViewModel class FollowPackMembersUserFeedViewModel( val followPackNote: AddressableNote, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeScreen.kt index bc4a6b369b..8ff65412ac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeScreen.kt @@ -92,6 +92,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.zonedDrawerSwipeIfModal import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.NewGeoPostButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.NewHashtagPostButton import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding @@ -102,8 +104,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.TabRowHeight import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.backups.BackupConflictCards -import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.NewGeoPostButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.NewHashtagPostButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.live.RenderEphemeralBubble import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.live.RenderGeohashBubble diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index e73f02e3ce..0bf885e812 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -128,6 +128,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.AddZapraise import com.vitorpamplona.amethyst.commons.ui.note.creators.zapraiser.ZapRaiserRequest import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.ForwardZapToButton import com.vitorpamplona.amethyst.commons.ui.note.types.ReplyRenderType +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -172,7 +173,6 @@ import com.vitorpamplona.amethyst.ui.note.creators.scheduling.roundUpToNextQuart import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription import com.vitorpamplona.amethyst.ui.note.creators.zappolls.ZapPollField import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.quartz.nip01Core.core.HexKey import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt index fcd6571a7a..82091a5d8a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/hashtags/TabFollowedTags.kt @@ -35,9 +35,9 @@ import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserTagFollows import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.HashtagHeader import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.HashtagHeader @Composable fun TabFollowedTags( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt index eab7549d10..7a3267b385 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.resources.send_kind0_to_local_relay_ti import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding @@ -48,7 +49,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow @Composable fun LocalRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index e83fea8d75..8de85d3e54 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -67,6 +67,9 @@ 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.SettingsSearchTopBar import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsItem +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.settings.SettingsCategory import com.vitorpamplona.amethyst.commons.ui.settings.SettingsEntry import com.vitorpamplona.amethyst.commons.ui.settings.SettingsIcon diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt index 139504d5d2..5fbd6cc27d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AppSettingsScreen.kt @@ -120,6 +120,11 @@ 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.screen.loggedIn.settings.SegmentedChoiceTile +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsBlockTile +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsControlRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.ui.theme.contentColorOnAccent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ComposeSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ComposeSettingsScreen.kt index 38f77ce538..d6067bc225 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ComposeSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ComposeSettingsScreen.kt @@ -96,6 +96,12 @@ import com.vitorpamplona.amethyst.commons.service.pow.PoWCategory import com.vitorpamplona.amethyst.commons.service.pow.PoWEstimator import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsBlockTile +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsControlRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsStepper +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSubControlRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NIP47SetupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NIP47SetupScreen.kt index 5a62043f6c..111c7bf346 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NIP47SetupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NIP47SetupScreen.kt @@ -52,6 +52,7 @@ import com.vitorpamplona.amethyst.commons.resources.payment_targets_section_expl import com.vitorpamplona.amethyst.commons.resources.wallet_connect import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.settings.SettingsCategory import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.SettingsCategorySpacingModifier @@ -63,7 +64,6 @@ import com.vitorpamplona.amethyst.ui.actions.paymentTargets.PaymentTargetsViewMo import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountContent import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.quartz.nipA3PaymentTargets.PaymentTarget @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt index 22cdf51516..100380e1fe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/NotificationSettingsScreen.kt @@ -89,6 +89,10 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsItem +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSwitchTile import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ResourceUsageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ResourceUsageScreen.kt index 84d81bfe39..e20138f7ec 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ResourceUsageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ResourceUsageScreen.kt @@ -138,6 +138,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeToMessage import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.allGoodColor import com.vitorpamplona.amethyst.commons.ui.theme.warningColor diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedView.kt index 3851b8fd57..fd87802cd2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedView.kt @@ -42,6 +42,7 @@ import com.vitorpamplona.amethyst.commons.resources.refresh import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled import com.vitorpamplona.amethyst.commons.ui.feeds.FeedError import com.vitorpamplona.amethyst.commons.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.StringFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt index fc09ddd1dd..0b8f3f2a38 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/UserSettingsScreen.kt @@ -84,6 +84,7 @@ import com.vitorpamplona.amethyst.commons.resources.user_preferences 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.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt index 8aac8ee458..2844f928ee 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt @@ -77,9 +77,9 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_group_unavailable_deta import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt index c9da5eb2ec..0bc29f3779 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt @@ -84,10 +84,10 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.model.cordn.AndroidCordnBlobStore -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.quartz.cordn.appMultiDevice.CordnHandoffCode import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt index 3b7d3f2b63..f926d7fdc5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt @@ -81,6 +81,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt index f978af2d63..ff4709d301 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt @@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.commons.model.cache.LocalCache +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nipBCOnchainZaps.chain.BitcoinAddressTx diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt index 0ae375f82d..7ca59ebe05 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt @@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_sats import com.vitorpamplona.amethyst.commons.resources.wallet_send import com.vitorpamplona.amethyst.commons.resources.wallet_transactions import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import java.text.NumberFormat diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt index 2b3a8a19f1..cfee01025f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt @@ -75,6 +75,8 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_receive import com.vitorpamplona.amethyst.commons.resources.wallet_sats import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.ReceiveState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import java.text.NumberFormat diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt index 1512dc514d..6fe151f908 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt @@ -107,6 +107,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.experimental.clink.debits.DebitFrequency @@ -475,10 +477,10 @@ private fun WalletCard( } } - if (walletInfo.error != null) { + walletInfo.error?.let { error -> Spacer(modifier = Modifier.height(4.dp)) Text( - text = walletInfo.error, + text = error, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt index b0d036312a..0c35d86bdc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt @@ -64,6 +64,8 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_payment_sending import com.vitorpamplona.amethyst.commons.resources.wallet_payment_success import com.vitorpamplona.amethyst.commons.resources.wallet_send import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.SendState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt index f1bdad6aa6..4cd03b8a5b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt @@ -78,6 +78,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.note.formatMonthDayTime diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt index 01ef24d512..1d73afba3c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt @@ -87,12 +87,12 @@ import com.vitorpamplona.amethyst.commons.tor.parseTorType import com.vitorpamplona.amethyst.commons.tor.resourceId import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CancellationException import org.jetbrains.compose.resources.StringResource diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerItemVisibilityTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerItemVisibilityTest.kt index 11ecb7f0f8..6da8a404e3 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerItemVisibilityTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerItemVisibilityTest.kt @@ -23,9 +23,9 @@ package com.vitorpamplona.amethyst.navigation import com.vitorpamplona.amethyst.commons.model.navigation.DrawerItemVisibility import com.vitorpamplona.amethyst.commons.model.navigation.DrawerSectionId import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionVisibility -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.DrawerSettingsState +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSectionVisibility +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSections +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.DrawerSettingsState import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt index 10a8563d51..537e1999e0 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/navigation/DrawerSectionsTest.kt @@ -25,8 +25,8 @@ import com.vitorpamplona.amethyst.commons.model.navigation.MandatoryDrawerItems import com.vitorpamplona.amethyst.commons.model.navigation.drawerSectionIdsFromNames import com.vitorpamplona.amethyst.commons.model.navigation.toNames import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections -import com.vitorpamplona.amethyst.ui.navigation.drawer.SdkGatedDrawerItems +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSections +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.SdkGatedDrawerItems import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Test 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 ca598c88ff..f12edcbbf0 100644 --- a/commons/plans/2026-09-27-one-ui-android-desktop.md +++ b/commons/plans/2026-09-27-one-ui-android-desktop.md @@ -356,10 +356,43 @@ assemblers, `EventSync`, …). Packages are renamed on the way: mint screens; relay and relay-group members; communities; Event Sync; the vanish screens; and the feed filter spinner with the feed views they share. `ViewModelProvider.Factory` implementations now override the multiplatform `create(KClass, CreationExtras)`. - - **Next:** re-measure; the exits now in front are the upload/gallery stack, the - Android-only `AccountViewModel` actions (`urlPreview`, media saving), `LoadCityName`, - `ReactionsRow` imports, the remaining `Amethyst.instance` reads (location, favorites, - Tor, browser icons, scheduled posts) and `AppSettingsScreen`. + - **Wave 3 cuts:** + - An `AppServices` port (`commons/service`) holds the app-wide stores screens read: + favorites, browser history and favicons, napplet and signer permissions, Tor + settings. It is installed as `LocalAppServices`; `AccountViewModelHost` gained the + Tor relay evaluation and the NIP-42 auth state. + - `AppPlatform` gained the camera QR scanner, an `AppLauncher` (favorite apps and web + links) and the Concord/napplet/manifest/favicon image models. + - The QR drawer is shared. Encoding sits behind `encodeQrMatrix`: zxing core on + JVM/Android, nothing yet on iOS. `KeepScreenBrightAndAwake` is expect/actual. + - `uriToRoute` left `MainActivity`. `ReactionsRow` and `LoadCityName` are imported + through the note-platform shims. Settings rows left `AppSettingsScreen`. + `formatGrouped` replaced `NumberFormat`. + - **Wave 3 moved:** 15 screens, 47 files. That covers the browser, favorite apps, + connected apps, bottom-bar settings, public chats, relay info, relay-group browse, the + Buzz boards and invite, Concord home, backup conflicts, show-QR and software-app + detail. `AppBottomBar` moved with them. + - **Main-thread checker deleted:** it was a debug-only Android assertion. Once the cache + and feeds were shared, it could only fire through a host hook that is a no-op + everywhere else. + - **Wave 4 cuts:** + - `@SuppressLint` became `@Suppress` app-wide. + - `formatHistoryReachDate` is expect/actual. + - quartz's `ConcurrentSet`/`ConcurrentMap` replaced `java.util.concurrent` in the Buzz + view models. + - Settings painter icons are `DrawableResource`s. + - Share buttons use `rememberTextSharer`. + - `AppPlatform.isCastingAvailable` and `supportsEmbeddedAppTabs` replace + `BuildConfig` and an API-level check. + - More of the synchronous-tier strings now have Compose copies. + - **Wave 4 moved:** 28 screens, 66 files: hashtag, relay and geohash feeds; people lists + and follow packs; the hidden/blocked/muted/spam settings; the Cordn hub, key-package, + link and coordinator screens; relay auth, privacy, home tabs, security filters, profile + UI, drawer and video-player settings; Cashu wallet, NWC and CLINK setup; the Concord + channel list and invite links; the attestation screen and the redirect loader. + - **Next:** re-measure. Known exits: the upload/gallery stack, the Android-only + `AccountViewModel` actions, `CalendarTimeFormat`, the chat views, `NestUiState`, + `NappletLauncher`, `UpdateZapAmountDialog` (biometrics), and the `java.time` pickers. 7. **Navigation**: the library swap, then `AppNavigation` + rail + drawer + bottom bar. 8. **The app root port** and the new JVM shim. Then the Desktop feature inventory, and retiring the old `desktopApp`. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt index 4ac7462573..92834fc256 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/authCommand/compose/RelayAuthComposeHelpers.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/relayClient/authCommand/compose/RelayAuthComposeHelpers.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.relayClient.authCommand.compose +package com.vitorpamplona.amethyst.commons.relayClient.authCommand.compose import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -48,7 +48,7 @@ import org.jetbrains.compose.resources.StringResource * settings screen, which both render a person by pubkey while their metadata streams in. */ @Composable -internal fun LoadRelayAuthUser( +fun LoadRelayAuthUser( pubkey: HexKey, content: @Composable (User?) -> Unit, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSectionVisibility.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/drawer/DrawerSectionVisibility.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSectionVisibility.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/drawer/DrawerSectionVisibility.kt index 438305ddcc..f50be35832 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSectionVisibility.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/drawer/DrawerSectionVisibility.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.navigation.drawer +package com.vitorpamplona.amethyst.commons.ui.navigation.drawer import com.vitorpamplona.amethyst.commons.model.navigation.DrawerItemVisibility import com.vitorpamplona.amethyst.commons.model.navigation.MandatoryDrawerItems diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/drawer/DrawerSections.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/drawer/DrawerSections.kt index fce50b38a5..81f99e9299 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/drawer/DrawerSections.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/drawer/DrawerSections.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.navigation.drawer +package com.vitorpamplona.amethyst.commons.ui.navigation.drawer import androidx.compose.runtime.Immutable import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/UserFeedViewModel.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/UserFeedViewModel.kt index 9e9e28d409..f176b4e12f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/UserFeedViewModel.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 +package com.vitorpamplona.amethyst.commons.ui.screen import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable @@ -36,6 +36,7 @@ import com.vitorpamplona.quartz.utils.Log import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt index 61fef65bf3..451c3a7095 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentAttestationScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentAttestationScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -105,6 +105,7 @@ import com.vitorpamplona.quartz.nip01Core.core.isValid import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt index e0d974d496..6e7b39807f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/cordnGroup/CordnGroupInfoScreen.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.chats.cordnGroup +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.clickable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt index a1c7a51c3f..7d8dd0ad02 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelListScreen.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.chats.publicChannels.concord +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -603,7 +603,7 @@ private fun rememberConcordDisplayName( * so leaving is what actually retires the community for them. */ @Composable -internal fun ConcordLeaveDialog( +fun ConcordLeaveDialog( communityName: String, isOwner: Boolean, onDismiss: () -> Unit, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt index 103668476f..b36cd8cd6a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordInviteLinksScreen.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.chats.publicChannels.concord +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord 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/geocaches/dal/GeocachesHereFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedFilter.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedFilter.kt index eb4c2505b5..37aafe909f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedFilter.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.geocaches.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.dal import com.vitorpamplona.amethyst.commons.feeds.AdditiveFeedFilter import com.vitorpamplona.amethyst.commons.geocaches.dal.GeocachesFeedFilter diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedViewModel.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedViewModel.kt index c97d568fc3..51de6e3a4f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/dal/GeocachesHereFeedViewModel.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.geocaches.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.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.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass /** * Drives the "N caches here" chip on a geohash screen. @@ -45,6 +47,9 @@ class GeocachesHereFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = GeocachesHereFeedViewModel(geohash, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = GeocachesHereFeedViewModel(geohash, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/GeoHashScreen.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/GeoHashScreen.kt index b962a15678..5aca79bd14 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/GeoHashScreen.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.geohash +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash import androidx.compose.foundation.layout.Column import androidx.compose.material3.IconButton @@ -26,7 +26,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.profile.ui.FollowButton @@ -38,6 +37,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_unfollow import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.search.SearchSeed +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded @@ -46,10 +46,10 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SearchTopBarActi import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton import com.vitorpamplona.amethyst.commons.ui.note.platform.LoadCityName import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.dal.GeoHashFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.datasource.GeoHashFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.dal.GeoHashFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.datasource.GeoHashFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.icons.symbols.Icon as SymbolIcon @Composable @@ -71,7 +71,7 @@ fun PrepareViewModelsGeoHashScreen( nav: INav, ) { val geohashViewModel: GeoHashFeedViewModel = - viewModel( + rememberViewModel( key = tag.geohash + "GeoHashFeedViewModel", factory = GeoHashFeedViewModel.Factory( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeocachesHereChip.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/GeocachesHereChip.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeocachesHereChip.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/GeocachesHereChip.kt index 2bf49a987c..d35ab182b2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeocachesHereChip.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/GeocachesHereChip.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.geohash +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -37,7 +37,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.feeds.FeedState import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -45,13 +44,14 @@ import com.vitorpamplona.amethyst.commons.model.navigation.GeocacheTab import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.geocache_caches_here +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.rememberGeocachePalette import com.vitorpamplona.amethyst.commons.ui.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.dal.GeocachesHereFeedViewModel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.datasource.GeocachesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.dal.GeocachesHereFeedViewModel /** * "N caches here", shown on a geohash feed when there are any. @@ -73,7 +73,7 @@ fun GeocachesHereChip( nav: INav, ) { val model: GeocachesHereFeedViewModel = - viewModel( + rememberViewModel( key = geohash + "GeocachesHereFeedViewModel", factory = GeocachesHereFeedViewModel.Factory(geohash, accountViewModel.account), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/NewGeoNoteButton.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/NewGeoNoteButton.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/NewGeoNoteButton.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/NewGeoNoteButton.kt index 8fa7a5f5e8..571c115e8a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/NewGeoNoteButton.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/NewGeoNoteButton.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.geohash +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.FloatingActionButton diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/dal/GeoHashFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/dal/GeoHashFeedFilter.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/dal/GeoHashFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/dal/GeoHashFeedFilter.kt index 88a241678f..5a4539892d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/dal/GeoHashFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/dal/GeoHashFeedFilter.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.geohash.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.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/geohash/dal/GeoHashFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/dal/GeoHashFeedViewModel.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/dal/GeoHashFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/dal/GeoHashFeedViewModel.kt index 4fd0ea3b81..9dc92f368e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/dal/GeoHashFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/dal/GeoHashFeedViewModel.kt @@ -18,15 +18,17 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.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.cache.LocalCache import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import kotlin.reflect.KClass @Stable class GeoHashFeedViewModel( @@ -43,6 +45,9 @@ class GeoHashFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = GeoHashFeedViewModel(geohash, relays, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = GeoHashFeedViewModel(geohash, relays, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt index 0c9abc4a88..eee6e40dc2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geohash/datasource/GeoHashFilterAssemblerSubscription.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.geohash.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagFeedLoaded.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/HashtagFeedLoaded.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagFeedLoaded.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/HashtagFeedLoaded.kt index ced407187e..954c75bd1d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagFeedLoaded.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/HashtagFeedLoaded.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.hashtag +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/HashtagScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/HashtagScreen.kt index 8957b08403..f2348e55d6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/HashtagScreen.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.hashtag +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -38,7 +38,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight 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.navigation.Route @@ -56,6 +55,7 @@ import com.vitorpamplona.amethyst.commons.resources.mute_hashtag import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.resources.unmute_hashtag import com.vitorpamplona.amethyst.commons.search.SearchSeed +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded @@ -63,11 +63,11 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SearchTopBarAction import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.dal.HashtagFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.datasource.HashtagFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.dal.HashtagFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource.HashtagFilterAssemblerSubscription @Composable fun HashtagScreen( @@ -88,7 +88,7 @@ fun PrepareViewModelsHashtagScreen( nav: INav, ) { val hashtagFeedViewModel: HashtagFeedViewModel = - viewModel( + rememberViewModel( key = tag.hashtag + "HashtagFeedViewModel", factory = HashtagFeedViewModel.Factory( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/NewHashtagNoteButton.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/NewHashtagNoteButton.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/NewHashtagNoteButton.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/NewHashtagNoteButton.kt index 97f7b7320a..5f9aac15b6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/NewHashtagNoteButton.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/NewHashtagNoteButton.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.hashtag +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.FloatingActionButton diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/dal/HashtagFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/dal/HashtagFeedFilter.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/dal/HashtagFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/dal/HashtagFeedFilter.kt index 3ecbdcc74f..65f5223e4d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/dal/HashtagFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/dal/HashtagFeedFilter.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.hashtag.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.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/hashtag/dal/HashtagFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/dal/HashtagFeedViewModel.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/dal/HashtagFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/dal/HashtagFeedViewModel.kt index ddb32f8d8f..08c622343f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/dal/HashtagFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/dal/HashtagFeedViewModel.kt @@ -18,15 +18,17 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.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.cache.LocalCache import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import kotlin.reflect.KClass @Stable class HashtagFeedViewModel( @@ -43,6 +45,9 @@ class HashtagFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = HashtagFeedViewModel(hashtag, relays, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = HashtagFeedViewModel(hashtag, relays, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssemblerSubscription.kt index 02660106a7..2425d5105e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/hashtag/datasource/HashtagFilterAssemblerSubscription.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.hashtag.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/PeopleListView.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/PeopleListView.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/PeopleListView.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/PeopleListView.kt index ea29830159..a02a03d243 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/PeopleListView.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/PeopleListView.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.lists.display +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/ShowUserSuggestions.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/ShowUserSuggestions.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/ShowUserSuggestions.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/ShowUserSuggestions.kt index 585ec8d72b..7a8c628816 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/ShowUserSuggestions.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/ShowUserSuggestions.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.lists.display +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -85,6 +85,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt index e929462e5e..e9396f3bc2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/lists/PeopleListScreen.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.lists.display.lists +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.lists import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -63,7 +63,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.AddressableNote import com.vitorpamplona.amethyst.commons.model.User @@ -86,6 +85,7 @@ 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.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -93,6 +93,9 @@ import com.vitorpamplona.amethyst.commons.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.commons.ui.note.ClearTextIcon import com.vitorpamplona.amethyst.commons.ui.note.VerticalDotsIcon import com.vitorpamplona.amethyst.commons.ui.note.externalLinkForNote +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.DrawUser +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.PeopleListView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.RenderAddUserFieldAndSuggestions import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -104,9 +107,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.TabRowHeight import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.DrawUser -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.PeopleListView -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.RenderAddUserFieldAndSuggestions import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch @@ -118,7 +118,7 @@ fun PeopleListScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val viewModel: PeopleListViewModel = viewModel() + val viewModel: PeopleListViewModel = rememberViewModel { PeopleListViewModel() } viewModel.init(accountViewModel, selectedDTag) val pagerState = rememberPagerState { 2 } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/lists/PeopleListViewModel.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/lists/PeopleListViewModel.kt index 15ed464437..bd5d8ed5f3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/lists/PeopleListViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/lists/PeopleListViewModel.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.lists.display.lists +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.lists import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip51Lists.followSet.FollowSetEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt index 2f91f2666d..c6b42a6d50 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/packs/FollowPackScreen.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.lists.display.packs +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.packs import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -56,7 +56,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.AddressableNote import com.vitorpamplona.amethyst.commons.model.User @@ -75,6 +74,7 @@ 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.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -82,6 +82,9 @@ import com.vitorpamplona.amethyst.commons.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.commons.ui.note.ClearTextIcon import com.vitorpamplona.amethyst.commons.ui.note.VerticalDotsIcon import com.vitorpamplona.amethyst.commons.ui.note.externalLinkForNote +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.DrawUser +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.PeopleListView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.RenderAddUserFieldAndSuggestions import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -92,9 +95,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.DrawUser -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.PeopleListView -import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.display.RenderAddUserFieldAndSuggestions import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow @@ -105,7 +105,7 @@ fun FollowPackScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val viewModel: FollowPackViewModel = viewModel() + val viewModel: FollowPackViewModel = rememberViewModel { FollowPackViewModel() } viewModel.init(accountViewModel, selectedDTag) Scaffold( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/packs/FollowPackViewModel.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/packs/FollowPackViewModel.kt index cf7e4f725b..922843cab4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/display/packs/FollowPackViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/lists/display/packs/FollowPackViewModel.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.lists.display.packs +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.packs import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt index fde68ca8f8..3810d2f146 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/privacy/PrivacyOptionsScreen.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.privacy +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.privacy import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically @@ -103,15 +103,15 @@ 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.LocalAppServices +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 +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSwitchTile import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SegmentedChoiceTile -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsBlockTile -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsControlRow -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsDivider -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSwitchTile import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import org.jetbrains.compose.resources.StringResource diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt index 0f42354ae0..99835b3daf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/redirect/LoadRedirectScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/redirect/LoadRedirectScreen.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.redirect +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.redirect import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -51,6 +51,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensible import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.withContext /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/NewRelayNoteButton.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/NewRelayNoteButton.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/NewRelayNoteButton.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/NewRelayNoteButton.kt index 2597c7e193..6e1fe0d881 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/NewRelayNoteButton.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/NewRelayNoteButton.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.relay +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.FloatingActionButton diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/RelayFeedScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/RelayFeedScreen.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/RelayFeedScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/RelayFeedScreen.kt index 7951cff5e7..f6f9c14465 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/RelayFeedScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/RelayFeedScreen.kt @@ -18,13 +18,12 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.relay +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.profile.ui.FollowButton import com.vitorpamplona.amethyst.commons.profile.ui.UnfollowButton @@ -33,15 +32,16 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_follow import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_unfollow import com.vitorpamplona.amethyst.commons.resources.read_only_user +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay.dal.SingleRelayFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay.datasource.RelayFeedFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relay.dal.SingleRelayFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relay.datasource.RelayFeedFilterAssemblerSubscription import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer @@ -67,7 +67,7 @@ fun PrepareViewModelsRelayFeedScreen( nav: INav, ) { val feedViewModel: SingleRelayFeedViewModel = - viewModel( + rememberViewModel( key = normalizedUrl.url + "SingleRelayFeedViewModel", factory = SingleRelayFeedViewModel.Factory( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/dal/RelayFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/dal/RelayFeedFilter.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/dal/RelayFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/dal/RelayFeedFilter.kt index 197a39408e..658b7c0cd0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/dal/RelayFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/dal/RelayFeedFilter.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.relay.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay.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/relay/dal/SingleRelayFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/dal/SingleRelayFeedViewModel.kt similarity index 84% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/dal/SingleRelayFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/dal/SingleRelayFeedViewModel.kt index f4ec796d47..ec1489e1ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/dal/SingleRelayFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/dal/SingleRelayFeedViewModel.kt @@ -18,15 +18,17 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.relay.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay.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.cache.LocalCache import com.vitorpamplona.amethyst.commons.ui.screen.AndroidFeedViewModel import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import kotlin.reflect.KClass @Stable class SingleRelayFeedViewModel( @@ -40,6 +42,9 @@ class SingleRelayFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = SingleRelayFeedViewModel(relayUrl, account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = SingleRelayFeedViewModel(relayUrl, account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/datasource/RelayFeedFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/datasource/RelayFeedFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/datasource/RelayFeedFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/datasource/RelayFeedFilterAssemblerSubscription.kt index c1bf03daa0..e866409b66 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relay/datasource/RelayFeedFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relay/datasource/RelayFeedFilterAssemblerSubscription.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.relay.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relay.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt index dffe0b19b5..4063628228 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relayauth/RelayAuthSettingsScreen.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.relayauth +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relayauth import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -62,7 +62,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape -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 @@ -71,6 +70,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.nip11RelayInfo.loadRelayInfo +import com.vitorpamplona.amethyst.commons.relayClient.authCommand.compose.relayAuthPurposeLabelRes import com.vitorpamplona.amethyst.commons.relayauth.AuthPurposeKind import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPermissionStore @@ -115,18 +115,18 @@ 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.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSwitchTile import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.MediumRelayIconModifier import com.vitorpamplona.amethyst.commons.ui.theme.RelayIconFilter import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.relayAuthPurposeLabelRes -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsDivider -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSwitchTile import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrlOrNull import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -199,7 +199,7 @@ fun RelayAuthSettingsScreen( val display = url.normalizeRelayUrlOrNull()?.displayUrl() ?: url val result = snackbarHostState.showSnackbar( - message = sessionForgottenLabel.format(display), + message = sessionForgottenLabel.replace("%1\$s", display), actionLabel = undoLabel, withDismissAction = true, ) @@ -209,7 +209,7 @@ fun RelayAuthSettingsScreen( // instead of leaving a tapped undo looking like it silently did nothing. if (result == SnackbarResult.ActionPerformed && !ledger.grantForSession(url)) { snackbarHostState.showSnackbar( - message = sessionUndoBlockedLabel.format(display), + message = sessionUndoBlockedLabel.replace("%1\$s", display), withDismissAction = true, ) } @@ -225,7 +225,7 @@ fun RelayAuthSettingsScreen( reloadKey++ val result = snackbarHostState.showSnackbar( - message = removedLabel.format(url.normalizeRelayUrlOrNull()?.displayUrl() ?: url), + message = removedLabel.replace("%1\$s", url.normalizeRelayUrlOrNull()?.displayUrl() ?: url), actionLabel = undoLabel, withDismissAction = true, ) @@ -650,7 +650,6 @@ private fun RecentLoginRow( nav: INav, onPromote: (RelayAuthDecision) -> Unit, ) { - val context = LocalContext.current RelayRowFrame( url = url, accountViewModel = accountViewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BlockedUsersScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BlockedUsersScreen.kt similarity index 89% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BlockedUsersScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BlockedUsersScreen.kt index d5c048507f..f6cb8a5d51 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/BlockedUsersScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/BlockedUsersScreen.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.padding import androidx.compose.material3.Scaffold @@ -28,13 +28,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.blocked_users import com.vitorpamplona.amethyst.commons.resources.security_blocked_users_empty +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.dal.HiddenAccountsFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.dal.HiddenAccountsFeedViewModel @Composable fun BlockedUsersScreen( @@ -42,7 +42,7 @@ fun BlockedUsersScreen( nav: INav, ) { val viewModel: HiddenAccountsFeedViewModel = - viewModel(factory = HiddenAccountsFeedViewModel.Factory(accountViewModel.account)) + rememberViewModel(factory = HiddenAccountsFeedViewModel.Factory(accountViewModel.account)) InvalidateOnBlockListChange(accountViewModel) { viewModel.invalidateData() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt index b1f3b72604..ebacc2172e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/DrawerSettingsScreen.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.Arrangement import androidx.compose.foundation.layout.Column @@ -59,6 +59,9 @@ import com.vitorpamplona.amethyst.commons.resources.drawer_settings_show_all import com.vitorpamplona.amethyst.commons.resources.drawer_settings_title import com.vitorpamplona.amethyst.commons.resources.drawer_settings_visible import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.NavBarCatalog +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSection +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSectionVisibility +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSections 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 @@ -76,9 +79,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSection -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionVisibility -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSections @Composable @Preview(device = "spec:width=2100px,height=2340px,dpi=440") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsState.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/DrawerSettingsState.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsState.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/DrawerSettingsState.kt index fa5095f6f5..1d7676b88a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/DrawerSettingsState.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/DrawerSettingsState.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.runtime.Stable import androidx.compose.runtime.getValue @@ -26,8 +26,8 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.vitorpamplona.amethyst.commons.model.navigation.DrawerItemVisibility import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSection -import com.vitorpamplona.amethyst.ui.navigation.drawer.DrawerSectionVisibility +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSection +import com.vitorpamplona.amethyst.commons.ui.navigation.drawer.DrawerSectionVisibility /** * State holder for the Side Menu settings screen: owns the set of switched-off drawer rows and the diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/HiddenWordsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/HiddenWordsScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/HiddenWordsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/HiddenWordsScreen.kt index d7db6d743b..b9cb2481cc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/HiddenWordsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/HiddenWordsScreen.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.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -59,7 +59,6 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.feeds.StringFeedState import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.hidden_words @@ -69,18 +68,19 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.resources.security_hidden_words_empty import com.vitorpamplona.amethyst.commons.ui.components.AddButton +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.FeedError import com.vitorpamplona.amethyst.commons.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.commons.ui.insets.rememberSafeImeInsets import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.ShowUserButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.dal.HiddenWordsFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.HorzPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.dal.HiddenWordsFeedViewModel @Composable fun HiddenWordsScreen( @@ -88,7 +88,7 @@ fun HiddenWordsScreen( nav: INav, ) { val viewModel: HiddenWordsFeedViewModel = - viewModel(factory = HiddenWordsFeedViewModel.Factory(accountViewModel.account)) + rememberViewModel(factory = HiddenWordsFeedViewModel.Factory(accountViewModel.account)) InvalidateOnBlockListChange(accountViewModel) { viewModel.invalidateData() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/HomeTabsSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/HomeTabsSettingsScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/HomeTabsSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/HomeTabsSettingsScreen.kt index 1a689f57c3..cacde05552 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/HomeTabsSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/HomeTabsSettingsScreen.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.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/MutedThreadsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/MutedThreadsScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/MutedThreadsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/MutedThreadsScreen.kt index 75bf656450..63e31207cf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/MutedThreadsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/MutedThreadsScreen.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.Box import androidx.compose.foundation.layout.Column @@ -43,7 +43,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.feeds.FeedState import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.resources.Res @@ -51,11 +50,13 @@ import com.vitorpamplona.amethyst.commons.resources.action_unmute import com.vitorpamplona.amethyst.commons.resources.settings_muted_threads_empty import com.vitorpamplona.amethyst.commons.resources.settings_muted_threads_title import com.vitorpamplona.amethyst.commons.resources.settings_muted_threads_unknown +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.FeedError import com.vitorpamplona.amethyst.commons.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.dal.MutedThreadsFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.commons.ui.theme.ButtonPadding @@ -64,7 +65,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp import com.vitorpamplona.amethyst.commons.ui.theme.Size15dp import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.dal.MutedThreadsFeedViewModel @Composable fun MutedThreadsScreen( @@ -72,7 +72,7 @@ fun MutedThreadsScreen( nav: INav, ) { val viewModel: MutedThreadsFeedViewModel = - viewModel(factory = MutedThreadsFeedViewModel.Factory(accountViewModel.account)) + rememberViewModel(factory = MutedThreadsFeedViewModel.Factory(accountViewModel.account)) InvalidateOnBlockListChange(accountViewModel) { viewModel.invalidateData() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ProfileUiSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/ProfileUiSettingsScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ProfileUiSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/ProfileUiSettingsScreen.kt index f3f4610603..6e93d99d94 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ProfileUiSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/ProfileUiSettingsScreen.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.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt index 8e1bfb4217..680649a54b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityFiltersScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SecurityFiltersScreen.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.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityListsCommon.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SecurityListsCommon.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityListsCommon.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SecurityListsCommon.kt index 495be15db8..11a618e52b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SecurityListsCommon.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SecurityListsCommon.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.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.ShowUserButton import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.commons.ui.note.buttons.CloseButton +import com.vitorpamplona.amethyst.commons.ui.screen.UserFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding @@ -77,7 +78,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp import com.vitorpamplona.amethyst.commons.ui.theme.Size15dp import com.vitorpamplona.amethyst.commons.ui.theme.Size55dp import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel import org.jetbrains.compose.resources.StringResource /** @@ -225,7 +225,7 @@ internal fun SelectableUserList( } @Composable -internal fun EmptyState(message: StringResource) { +fun EmptyState(message: StringResource) { Column( modifier = Modifier.fillMaxSize().padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsRows.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsRows.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsRows.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsRows.kt index 997034fd9d..e6e6a3a8c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsRows.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsRows.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.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsSectionCard.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsSectionCard.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsSectionCard.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsSectionCard.kt index 748091b5b9..648a48ba52 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsSectionCard.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsSectionCard.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.clickable @@ -58,7 +58,7 @@ import org.jetbrains.compose.resources.StringResource import androidx.compose.material3.Icon as Material3Icon @Composable -internal fun SettingsSection( +fun SettingsSection( title: StringResource, isDanger: Boolean = false, content: @Composable ColumnScope.() -> Unit, @@ -95,7 +95,7 @@ internal fun SettingsSection( } @Composable -internal fun SettingsDivider() { +fun SettingsDivider() { HorizontalDivider( modifier = Modifier.padding(start = 68.dp), thickness = 0.5.dp, @@ -105,7 +105,7 @@ internal fun SettingsDivider() { /** Navigation row: icon + title + optional [trailing] (count badge, etc.) + chevron. */ @Composable -internal fun SettingsItem( +fun SettingsItem( title: StringResource, icon: MaterialSymbol, isDanger: Boolean = false, @@ -129,7 +129,7 @@ internal fun SettingsItem( /** Navigation row variant taking a drawable painter as the leading icon. */ @Composable -internal fun SettingsItem( +fun SettingsItem( title: StringResource, iconPainter: DrawableResource, iconPainterRef: Int, @@ -214,7 +214,7 @@ private fun SettingsItemRow( * tapping anywhere should toggle the state. */ @Composable -internal fun SettingsControlRow( +fun SettingsControlRow( icon: MaterialSymbol, title: String, description: String, @@ -256,7 +256,7 @@ internal fun SettingsControlRow( /** A [SettingsControlRow] whose trailing control is a [Switch]; tapping anywhere toggles. */ @Composable -internal fun SettingsSwitchTile( +fun SettingsSwitchTile( icon: MaterialSymbol, title: StringResource, description: StringResource, @@ -280,7 +280,7 @@ internal fun SettingsSwitchTile( * (used to lock the last remaining item in a "keep at least one on" group). */ @Composable -internal fun SettingsSwitchTile( +fun SettingsSwitchTile( icon: MaterialSymbol, title: StringResource, checked: Boolean, @@ -323,7 +323,7 @@ internal fun SettingsSwitchTile( * that only matters when the parent toggle is on). Text dims when [enabled] is false. */ @Composable -internal fun SettingsSubControlRow( +fun SettingsSubControlRow( title: String, description: String, enabled: Boolean = true, @@ -358,7 +358,7 @@ internal fun SettingsSubControlRow( * SegmentedButtonRow). Use when the control would crowd a trailing slot. */ @Composable -internal fun SettingsBlockTile( +fun SettingsBlockTile( icon: MaterialSymbol, title: String, description: String, @@ -429,7 +429,7 @@ internal fun SettingsCountBadge(count: Int) { * `null` to always show the number. */ @Composable -internal fun SettingsStepper( +fun SettingsStepper( value: Int, min: Int, max: Int, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SpammingUsersScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SpammingUsersScreen.kt similarity index 89% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SpammingUsersScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SpammingUsersScreen.kt index 42ff801b75..75d55e3df7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SpammingUsersScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SpammingUsersScreen.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.padding import androidx.compose.material3.Scaffold @@ -28,13 +28,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.security_spamming_users_empty import com.vitorpamplona.amethyst.commons.resources.spamming_users +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.dal.SpammerAccountsFeedViewModel import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.dal.SpammerAccountsFeedViewModel @Composable fun SpammingUsersScreen( @@ -42,7 +42,7 @@ fun SpammingUsersScreen( nav: INav, ) { val viewModel: SpammerAccountsFeedViewModel = - viewModel(factory = SpammerAccountsFeedViewModel.Factory(accountViewModel.account)) + rememberViewModel(factory = SpammerAccountsFeedViewModel.Factory(accountViewModel.account)) InvalidateOnBlockListChange(accountViewModel) { viewModel.invalidateData() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/StringFeedViewModel.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/StringFeedViewModel.kt index da2b8a8563..627284eb44 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/StringFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/StringFeedViewModel.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.runtime.MutableState import androidx.compose.runtime.Stable @@ -35,6 +35,7 @@ import com.vitorpamplona.quartz.utils.Log import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt index 1361e34757..877ff37712 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/VideoPlayerSettingsScreen.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.animation.core.animateFloatAsState import androidx.compose.foundation.ScrollState diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt index f8659c8274..3bc74a3606 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.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.cordn +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -115,13 +115,13 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.timeAgoNoDot import com.vitorpamplona.amethyst.commons.ui.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CopyableKeyRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CoordinatorIdentityRow import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.coordinatorDisplayName import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.cordnGroup.CopyableKeyRow -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.quartz.cordn.spec00Coordinator.CoordinatorServerInfo import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnHubScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnHubScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnHubScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnHubScreen.kt index 0c820afc23..36e1311077 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnHubScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnHubScreen.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.cordn +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -55,13 +55,13 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_hub_title 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.screen.loggedIn.settings.SettingsControlRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsControlRow -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsDivider -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import org.jetbrains.compose.resources.StringResource /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt index 4ba0e5df10..5280a180ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.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.cordn +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -76,10 +76,10 @@ import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.pluralStringRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CoordinatorIdentityRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import kotlinx.coroutines.launch /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt index 946af4eeaa..f02e04858f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.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.cordn +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -70,10 +70,10 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_link_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.cordn.CoordinatorIdentityRow import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.quartz.nip01Core.core.HexKey import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedFilter.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedFilter.kt index 7cc9eb83c4..c0ba15dee9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedFilter.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.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.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/settings/dal/HiddenAccountsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt index c0debbb316..26c897aabb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt @@ -18,13 +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.settings.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.dal import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.UserFeedViewModel import kotlin.reflect.KClass class HiddenAccountsFeedViewModel( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenWordsFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenWordsFeedFilter.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenWordsFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenWordsFeedFilter.kt index 9c3b992610..7db8a143cd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenWordsFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenWordsFeedFilter.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.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.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/settings/dal/HiddenWordsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenWordsFeedViewModel.kt similarity index 78% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenWordsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenWordsFeedViewModel.kt index 1c752650c4..c450216e15 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/HiddenWordsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenWordsFeedViewModel.kt @@ -18,12 +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.settings.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.dal import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.StringFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.StringFeedViewModel +import kotlin.reflect.KClass class HiddenWordsFeedViewModel( val account: Account, @@ -34,6 +36,9 @@ class HiddenWordsFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = HiddenWordsFeedViewModel(account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = HiddenWordsFeedViewModel(account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/MutedThreadsFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/MutedThreadsFeedFilter.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/MutedThreadsFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/MutedThreadsFeedFilter.kt index 2d85c61b14..d58cdd8d97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/MutedThreadsFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/MutedThreadsFeedFilter.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.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.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/settings/dal/MutedThreadsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/MutedThreadsFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/MutedThreadsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/MutedThreadsFeedViewModel.kt index 4f415fa9f7..544f320996 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/MutedThreadsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/MutedThreadsFeedViewModel.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.settings.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.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.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class MutedThreadsFeedViewModel( @@ -34,6 +36,9 @@ class MutedThreadsFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = MutedThreadsFeedViewModel(account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = MutedThreadsFeedViewModel(account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedFilter.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedFilter.kt index b125a22065..1bed285d80 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedFilter.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.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.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/settings/dal/SpammerAccountsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedViewModel.kt similarity index 79% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedViewModel.kt index 353bb2bd9c..e27ceef912 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/SpammerAccountsFeedViewModel.kt @@ -18,12 +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.settings.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.dal import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.UserFeedViewModel +import kotlin.reflect.KClass class SpammerAccountsFeedViewModel( val account: Account, @@ -32,6 +34,9 @@ class SpammerAccountsFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = SpammerAccountsFeedViewModel(account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = SpammerAccountsFeedViewModel(account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt index a3ab7b2946..dfa1d340da 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/AddClinkDebitWalletScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -49,7 +49,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard 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.resources.Res @@ -63,6 +62,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_name import com.vitorpamplona.amethyst.commons.resources.wallet_name_hint import com.vitorpamplona.amethyst.commons.resources.wallet_paste_ndebit import com.vitorpamplona.amethyst.commons.resources.wallet_save +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.components.util.getText import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -85,7 +85,7 @@ fun AddClinkDebitWalletScreen( nav: INav, ndebit: String? = null, ) { - val walletViewModel: WalletViewModel = viewModel() + val walletViewModel: WalletViewModel = rememberViewModel { WalletViewModel() } walletViewModel.init(accountViewModel) var walletName by remember { mutableStateOf("") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt index d31670a2d0..d5f64f549a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/AddNwcWalletScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/AddNwcWalletScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -51,7 +51,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalUriHandler 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.resources.Res @@ -67,6 +66,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_name import com.vitorpamplona.amethyst.commons.resources.wallet_name_hint import com.vitorpamplona.amethyst.commons.resources.wallet_paste_uri import com.vitorpamplona.amethyst.commons.resources.wallet_save +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.components.util.getText import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -89,7 +89,7 @@ fun AddNwcWalletScreen( nav: INav, nip47Uri: String? = null, ) { - val walletViewModel: WalletViewModel = viewModel() + val walletViewModel: WalletViewModel = rememberViewModel { WalletViewModel() } walletViewModel.init(accountViewModel) var walletName by remember { mutableStateOf("") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/CashuWalletScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/CashuWalletScreen.kt index a32fc0781c..5a644873c4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/CashuWalletScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/CashuWalletScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -77,7 +77,6 @@ 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 androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.hashtags.Cashu import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons import com.vitorpamplona.amethyst.commons.icons.symbols.Icon @@ -156,6 +155,7 @@ import com.vitorpamplona.amethyst.commons.resources.paste_from_clipboard import com.vitorpamplona.amethyst.commons.resources.topup_mint_action import com.vitorpamplona.amethyst.commons.resources.wallet_add_cashu_title import com.vitorpamplona.amethyst.commons.resources.wallet_sats +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.components.util.getText import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -191,7 +191,7 @@ fun CashuWalletScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val viewModel: CashuWalletViewModel = viewModel() + val viewModel: CashuWalletViewModel = rememberViewModel { CashuWalletViewModel() } // Synchronous init so state-flow getters don't hit a null `account` on // the first composition pass. init() is idempotent — just sets refs. viewModel.init(accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletViewModel.kt index be7ca8faed..c93d217a27 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletViewModel.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -67,6 +67,7 @@ import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceMethod import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceSuccessResponse import com.vitorpamplona.quartz.nip47WalletConnect.rpc.Response import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow From 3a391c835f77728d528addbea2ca859f043ec1ce Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 06:57:24 +0000 Subject: [PATCH 06/20] refactor: cut more shared-screen blockers (step 6 wave 5 seams) - PlatformBackHandler (commonsUI): AndroidX BackHandler on Android, Compose Multiplatform's ui-backhandler on desktop and iOS. The app's 21 BackHandler users switch to it. commonsUI adds androidx.activity:activity-compose (androidMain) and org.jetbrains.compose.ui:ui-backhandler (skikoMain); both Apache-2.0, both already shipped. - rememberLongNotice joins rememberShortNotice, so Toast.LENGTH_LONG messages keep their duration; four screens drop their Toasts for the shared notices. - The wallet screens use formatGrouped instead of NumberFormat and the shared Clipboard getText/setText extensions instead of ClipboardManager. - formatMonthDayTime (commonsUI) builds "May 28 14:32" from DateSkeletonFormatter("MMMd") and the system-clock time-of-day formatter; the two transaction lists use it. - UsageKeys uses quartz's ConcurrentMap. LocalCacheHost gains a UsageCounter (no-op by default, the Android host feeds its resource-usage ledger), so reconnectPoolAfterJoin no longer reaches for Amethyst.instance. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/AmethystLocalCacheHost.kt | 3 ++ .../service/resourceusage/UsageKeys.kt | 8 ++--- .../mediaServers/BlossomImportScreen.kt | 9 ++--- .../amethyst/ui/call/CallScreen.kt | 4 +-- .../nip22Comments/GenericCommentPostScreen.kt | 4 +-- .../AccountSwitcherAndLeftDrawerLayout.kt | 4 +-- .../loggedIn/badges/award/AwardBadgeScreen.kt | 4 +-- .../old/OldBookmarkListScreen.kt | 12 ++----- .../screen/loggedIn/browser/WebAppScreen.kt | 6 ++-- .../screen/loggedIn/buzz/BuzzCanvasScreen.kt | 4 +-- .../screen/loggedIn/buzz/BuzzJoinReconnect.kt | 7 ++-- .../chats/privateDM/send/NewGroupDMScreen.kt | 4 +-- .../send/PrivateMessageEditFieldRow.kt | 4 +-- .../chats/publicChannels/send/EditFieldRow.kt | 4 +-- .../nip23LongForm/LongFormPostScreen.kt | 4 +-- .../nip99Classifieds/NewProductScreen.kt | 4 +-- .../screen/loggedIn/embed/EmbeddedTabLayer.kt | 6 ++-- .../loggedIn/favorites/NostrAppScreen.kt | 4 +-- .../loggedIn/gitRepo/code/GitCodeTab.kt | 6 ++-- .../loggedIn/gitRepo/code/GitCommitLog.kt | 6 ++-- .../loggedIn/home/ShortNotePostScreen.kt | 4 +-- .../screen/loggedIn/home/VoiceReplyScreen.kt | 4 +-- .../loggedIn/home/nip75Goals/NewGoalScreen.kt | 4 +-- .../room/activity/NestActivityContent.kt | 4 +-- .../publicMessages/NewPublicMessageScreen.kt | 4 +-- .../loggedIn/settings/AllSettingsScreen.kt | 11 +++--- .../screen/loggedIn/wallet/OnchainSection.kt | 4 +-- .../wallet/OnchainTransactionsScreen.kt | 15 ++++---- .../loggedIn/wallet/WalletDetailScreen.kt | 5 ++- .../loggedIn/wallet/WalletReceiveScreen.kt | 19 +++++----- .../ui/screen/loggedIn/wallet/WalletScreen.kt | 14 ++++---- .../loggedIn/wallet/WalletSendScreen.kt | 16 ++++----- .../wallet/WalletTransactionsScreen.kt | 15 ++++---- .../wallet/wizard/CashuWalletWizardScreen.kt | 4 +-- .../loggedIn/workouts/NewWorkoutScreen.kt | 4 +-- .../commons/model/cache/LocalCacheHost.kt | 4 +++ .../amethyst/commons/service/UsageCounter.kt | 34 ++++++++++++++++++ commonsUI/build.gradle.kts | 7 ++++ .../components/PlatformBackHandler.android.kt | 30 ++++++++++++++++ .../ui/components/ShortNotice.android.kt | 6 ++++ .../ui/components/PlatformBackHandler.kt | 33 +++++++++++++++++ .../commons/ui/components/ShortNotice.kt | 4 +++ .../amethyst/commons/ui/note/MonthDayTime.kt | 35 +++++++++++++++++++ .../commons/ui/components/ShortNotice.ios.kt | 3 ++ .../commons/ui/components/ShortNotice.jvm.kt | 3 ++ .../components/PlatformBackHandler.skiko.kt | 32 +++++++++++++++++ gradle/libs.versions.toml | 1 + 47 files changed, 301 insertions(+), 125 deletions(-) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/UsageCounter.kt create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.android.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt create mode 100644 commonsUI/src/skikoMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.skiko.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt index 4b97f1a2f8..7273c65a6b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AmethystLocalCacheHost.kt @@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.FileSystemNip95BlobStore import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.cache.LocalCacheHost import com.vitorpamplona.amethyst.commons.model.cache.Nip95BlobStore +import com.vitorpamplona.amethyst.commons.service.UsageCounter import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -54,6 +55,8 @@ class AmethystLocalCacheHost( override val relayStats: RelayStats get() = modules.relayStats + override val usageCounter: UsageCounter = UsageCounter { key, amount -> modules.resourceUsage.add(key, amount) } + override fun relaySelfPubKey(relay: NormalizedRelayUrl): HexKey? = modules.nip11Cache.getFromCache(relay).self override fun relayInfo(relay: NormalizedRelayUrl): Nip11RelayInformation = modules.nip11Cache.getFromCache(relay) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageKeys.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageKeys.kt index fd222cf993..179dc5e929 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageKeys.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageKeys.kt @@ -30,7 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.MachineReadabl import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.MachineReadablePrefix.RESTRICTED import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.MachineReadablePrefix.UNSUPPORTED import com.vitorpamplona.quartz.nip66RelayMonitor.reachability.RelayObserver -import java.util.concurrent.ConcurrentHashMap +import com.vitorpamplona.quartz.utils.concurrent.ConcurrentMap /** * Counter-key grammar for the resource-usage ledger. Keys are flat strings so @@ -489,8 +489,8 @@ object UsageKeys { private const val UP = "up" private const val DOWN = "down" - private val VERB_UP_KEYS = ConcurrentHashMap>() - private val VERB_DOWN_KEYS = ConcurrentHashMap>() + private val VERB_UP_KEYS = ConcurrentMap>() + private val VERB_DOWN_KEYS = ConcurrentMap>() /** * `relay.purpose.home_feed.sent` / `.bytes` — REQ frames and REQ bytes by the @@ -562,7 +562,7 @@ object UsageKeys { private val PURPOSE_SUFFIXES = arrayOf("sent", "bytes", "down", "downn", "dupbytes") - private val PURPOSE_KEYS = ConcurrentHashMap>() + private val PURPOSE_KEYS = ConcurrentMap>() /** The five `relay.purpose..*` keys, indexed by the `P_` constants above. */ private fun purposeKeys(purpose: String): Array = PURPOSE_KEYS.getOrPut(purpose) { Array(PURPOSE_SUFFIXES.size) { "relay.purpose.$purpose.${PURPOSE_SUFFIXES[it]}" } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/BlossomImportScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/BlossomImportScreen.kt index f6ff467acc..9da5c9ee73 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/BlossomImportScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/mediaServers/BlossomImportScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.actions.mediaServers -import android.widget.Toast import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -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.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp @@ -83,6 +81,7 @@ import com.vitorpamplona.amethyst.commons.resources.blossom_import_sources_secti import com.vitorpamplona.amethyst.commons.resources.blossom_import_start_button import com.vitorpamplona.amethyst.commons.resources.blossom_import_title import com.vitorpamplona.amethyst.commons.resources.delete_media_server +import com.vitorpamplona.amethyst.commons.ui.components.rememberLongNotice import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.pluralStringRes @@ -123,7 +122,7 @@ fun BlossomImportScreen( // kind-10063 list arrives from a relay just after the screen opens (common on cold start). val targetServers by accountViewModel.account.blossomServers.flow .collectAsStateWithLifecycle() - val context = LocalContext.current + val longNotice = rememberLongNotice() Scaffold( topBar = { @@ -254,9 +253,7 @@ fun BlossomImportScreen( when (vm.importSelected()) { is ImportStart.Started -> nav.popBack() ImportStart.Busy -> - Toast - .makeText(context, blossomImportBusyStr, Toast.LENGTH_LONG) - .show() + longNotice.show(blossomImportBusyStr) ImportStart.Empty -> {} } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallScreen.kt index 93d00acb4d..f91d87f7b2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/call/CallScreen.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.call import android.app.Activity import android.media.projection.MediaProjectionManager -import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background @@ -71,6 +70,7 @@ import com.vitorpamplona.amethyst.commons.resources.call_incoming import com.vitorpamplona.amethyst.commons.resources.call_incoming_video import com.vitorpamplona.amethyst.commons.resources.call_incoming_voice import com.vitorpamplona.amethyst.commons.resources.call_reject +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.call.session.CallSession @@ -98,7 +98,7 @@ fun CallScreen( val emptyStringFlow = remember { kotlinx.coroutines.flow.MutableStateFlow(null) } val errorMessage by (callSession?.errorMessage ?: emptyStringFlow).collectAsState() - BackHandler(enabled = callState !is CallState.Idle && callState !is CallState.Ended) { + PlatformBackHandler(enabled = callState !is CallState.Idle && callState !is CallState.Ended) { scope.launch { callManager.hangup() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index 492cf11b65..6c064c59c1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.note.nip22Comments import android.content.Intent import android.net.Uri -import androidx.activity.compose.BackHandler import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box @@ -79,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.resources.post_anonymously import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar import com.vitorpamplona.amethyst.commons.ui.note.BaseUserPicture @@ -227,7 +227,7 @@ fun GenericCommentPostScreen( StrippingFailureDialog(postViewModel.strippingFailureConfirmation) - BackHandler { + PlatformBackHandler { accountViewModel.launchSigner { postViewModel.sendDraftSync() onUiThread { postViewModel.cancel() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountSwitcherAndLeftDrawerLayout.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountSwitcherAndLeftDrawerLayout.kt index d601b4f9a2..ba3b6dfea6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountSwitcherAndLeftDrawerLayout.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountSwitcherAndLeftDrawerLayout.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import android.content.res.Configuration -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight @@ -48,6 +47,7 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.compose.currentBackStackEntryAsState import com.vitorpamplona.amethyst.commons.model.navigation.Route +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout import com.vitorpamplona.amethyst.commons.ui.layouts.NavigationStyle import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -182,7 +182,7 @@ private fun ModalDrawerShell( gesturesEnabled = drawerGesturesEnabled, drawerContent = { DrawerContent(nav, openSheet, accountViewModel) - BackHandler(enabled = nav.drawerState.isOpen, nav::closeDrawer) + PlatformBackHandler(enabled = nav.drawerState.isOpen, nav::closeDrawer) }, content = { if (showRail) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt index bcd80f4559..8c399774c1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.award -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -56,6 +55,7 @@ import com.vitorpamplona.amethyst.commons.resources.award_badge_remove_recipient import com.vitorpamplona.amethyst.commons.resources.award_badge_search_label import com.vitorpamplona.amethyst.commons.resources.award_badge_search_placeholder import com.vitorpamplona.amethyst.commons.ui.components.Nip05OrPubkeyLine +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar @@ -93,7 +93,7 @@ fun AwardBadgeScreen( onDispose { userSuggestions.reset() } } - BackHandler { + PlatformBackHandler { nav.popBack() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt index 00080bf8d5..28acf3594d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old -import android.widget.Toast import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -44,7 +43,6 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon @@ -58,6 +56,7 @@ import com.vitorpamplona.amethyst.commons.resources.old_bookmarks_title import com.vitorpamplona.amethyst.commons.resources.private_bookmarks import com.vitorpamplona.amethyst.commons.resources.public_bookmarks import com.vitorpamplona.amethyst.commons.ui.components.DeletedItemsBanner +import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded @@ -114,7 +113,7 @@ private fun RenderOldBookmarkScreen( ) { val pagerState = rememberPagerState { 2 } val coroutineScope = rememberCoroutineScope() - val context = LocalContext.current + val shortNotice = rememberShortNotice() val cache = accountViewModel.account.cache val deletedEventIds = remember(bookmarkState) { mutableSetOf() } @@ -177,12 +176,7 @@ private fun RenderOldBookmarkScreen( accountViewModel.launchSigner { accountViewModel.account.migrateOldBookmarksToNew() coroutineScope.launch { - Toast - .makeText( - context, - loadStringRes(Res.string.migrate_bookmarks_success), - Toast.LENGTH_SHORT, - ).show() + shortNotice.show(loadStringRes(Res.string.migrate_bookmarks_success)) } } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/WebAppScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/WebAppScreen.kt index 7a50ebb6f3..d2947e9dfc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/WebAppScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/browser/WebAppScreen.kt @@ -24,7 +24,6 @@ import android.Manifest import android.content.pm.PackageManager import android.os.Build import android.widget.Toast -import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi @@ -74,6 +73,7 @@ import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.model.navigation.favoriteIds import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.browser_unsupported +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -299,9 +299,9 @@ private fun EmbeddedWebAppTab( } val isFullscreen by controller.isFullscreen - BackHandler(enabled = canGoBack && !isFullscreen) { controller.back() } + PlatformBackHandler(enabled = canGoBack && !isFullscreen) { controller.back() } // A fullscreen video inside the tab: back leaves fullscreen first, as in Chrome. - BackHandler(enabled = isFullscreen) { controller.exitFullscreen() } + PlatformBackHandler(enabled = isFullscreen) { controller.exitFullscreen() } EmbeddedPageUi(controller, chrome.ui, showPageInfo, onPageInfoDismiss = { showPageInfo = false }) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt index ccf9e302cc..88b62bc0ac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -63,6 +62,7 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_canvas_empty import com.vitorpamplona.amethyst.commons.resources.buzz_canvas_save import com.vitorpamplona.amethyst.commons.resources.buzz_canvas_title import com.vitorpamplona.amethyst.commons.resources.cancel +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarExtensibleWithBackButton @@ -222,7 +222,7 @@ private fun CanvasEditor( val scope = rememberCoroutineScope() // Back cancels the edit and returns to the rendered canvas rather than leaving the screen. - BackHandler(enabled = !saving, onBack = onClose) + PlatformBackHandler(enabled = !saving, onBack = onClose) Scaffold( topBar = { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt index 9d419d535b..feadf3604a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz -import com.vitorpamplona.amethyst.Amethyst +import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -45,9 +45,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient * subscription — so `relay.trigger.buzz` undercounts joins, not re-challenges. */ internal fun reconnectPoolAfterJoin(client: INostrClient) { - // Guarded like every other ledger write that reaches the application singleton - // (MediaPlayTimeTracker, the workers): a diagnostics counter must never break a - // user-visible join, and `Amethyst.instance` is lateinit. - runCatching { Amethyst.instance.resourceUsage.add(UsageKeys.relayTrigger(UsageKeys.TRIGGER_BUZZ), 1) } + LocalCache.appHost.usageCounter.add(UsageKeys.relayTrigger(UsageKeys.TRIGGER_BUZZ), 1) client.reconnect(onlyIfChanged = false, ignoreRetryDelays = true) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index f5a15641f7..15c46d0264 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send import android.net.Uri -import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy import androidx.compose.foundation.layout.Box @@ -88,6 +87,7 @@ import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeToMessage @@ -181,7 +181,7 @@ fun NewGroupDMScreen( WatchAndLoadMyEmojiList(accountViewModel) - BackHandler { + PlatformBackHandler { accountViewModel.launchSigner { postViewModel.sendDraftSync() onUiThread { postViewModel.cancel() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt index ac480c057f..1df6ad0988 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -63,6 +62,7 @@ import com.vitorpamplona.amethyst.commons.resources.retry_without_encryption import com.vitorpamplona.amethyst.commons.resources.this_message_will_disappear_in import com.vitorpamplona.amethyst.commons.resources.upload_without_encryption_warning import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler 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.routes.routeFor @@ -118,7 +118,7 @@ fun PrivateMessageEditFieldRow( onSendNewMessage: () -> Unit, nav: INav, ) { - BackHandler { + PlatformBackHandler { if (channelScreenModel.message.text.isNotBlank()) { accountViewModel.launchSigner { channelScreenModel.sendDraftSync() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt index 6cb3e598d1..b4f2124ed2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -54,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_editing_banner import com.vitorpamplona.amethyst.commons.resources.cancel import com.vitorpamplona.amethyst.commons.resources.reply_here import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -82,7 +82,7 @@ fun EditFieldRow( onSendNewMessage: suspend () -> Unit, nav: INav, ) { - BackHandler { + PlatformBackHandler { accountViewModel.launchSigner { channelScreenModel.sendDraftSync() onUiThread { channelScreenModel.cancel() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt index c0932f7e1b..129fc19262 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm -import androidx.activity.compose.BackHandler import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -103,6 +102,7 @@ import com.vitorpamplona.amethyst.commons.resources.your_article_title import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.ContentSensitivityExplainer @@ -169,7 +169,7 @@ fun LongFormPostScreen( StrippingFailureDialog(postViewModel.strippingFailureConfirmation) - BackHandler { + PlatformBackHandler { accountViewModel.launchSigner { postViewModel.sendDraftSync() onUiThread { postViewModel.cancel() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index 0d283ddfb9..4536d7d26b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds -import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -53,6 +52,7 @@ import com.vitorpamplona.amethyst.commons.resources.lightning_invoice import com.vitorpamplona.amethyst.commons.resources.new_product import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -148,7 +148,7 @@ fun NewProductScreen( StrippingFailureDialog(postViewModel.strippingFailureConfirmation) - BackHandler { + PlatformBackHandler { accountViewModel.launchSigner { postViewModel.sendDraftSync() onUiThread { postViewModel.cancel() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt index ba49a8480c..e63004a330 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/embed/EmbeddedTabLayer.kt @@ -29,7 +29,6 @@ import android.graphics.BitmapFactory import android.os.Build import android.os.SystemClock import android.view.ViewGroup -import androidx.activity.compose.BackHandler import androidx.annotation.RequiresApi import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Canvas @@ -90,6 +89,7 @@ import com.vitorpamplona.amethyst.commons.browser.ui.pill.BrowserPillEvent import com.vitorpamplona.amethyst.commons.browser.ui.pill.ConsoleLine import com.vitorpamplona.amethyst.commons.browser.ui.pill.ConsoleSheet import com.vitorpamplona.amethyst.commons.browser.ui.pill.FindInPagePill +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.napplethost.BrowserWebTools import kotlinx.coroutines.delay import kotlinx.serialization.json.buildJsonObject @@ -306,7 +306,7 @@ fun EmbeddedTabLayer(barFavoriteIds: List) { } if (pillExpanded) { - BackHandler { pillExpanded = false } + PlatformBackHandler { pillExpanded = false } Box( Modifier .fillMaxSize() @@ -361,7 +361,7 @@ fun EmbeddedTabLayer(barFavoriteIds: List) { // Find in page: opened from the pill's Find tile. if (findShowing && findBridge != null) { - BackHandler { closeFind() } + PlatformBackHandler { closeFind() } val result by findBridge.findResult FindInPagePill( query = findQuery, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/NostrAppScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/NostrAppScreen.kt index 5dccbbf0dc..41306f42d5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/NostrAppScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/favorites/NostrAppScreen.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites import android.os.Build import android.widget.Toast -import androidx.activity.compose.BackHandler import androidx.annotation.RequiresApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize @@ -73,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.favorite_apps import com.vitorpamplona.amethyst.commons.resources.favorite_notice_paid import com.vitorpamplona.amethyst.commons.resources.favorite_notice_published import com.vitorpamplona.amethyst.commons.resources.favorite_notice_uploaded +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar @@ -262,7 +262,7 @@ private fun EmbeddedNostrAppTab( onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } - BackHandler(enabled = canGoBack) { controller.back() } + PlatformBackHandler(enabled = canGoBack) { controller.back() } if (showAccess) { Dialog(onDismissRequest = { showAccess = false }, properties = DialogProperties(usePlatformDefaultWidth = false)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCodeTab.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCodeTab.kt index 7926d67b00..04a9464841 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCodeTab.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCodeTab.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.code -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll @@ -73,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.git_repo_no_clone_url import com.vitorpamplona.amethyst.commons.resources.git_repo_no_search_results import com.vitorpamplona.amethyst.commons.resources.git_repo_root import com.vitorpamplona.amethyst.commons.resources.git_repo_search_files +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.layouts.LocalDisappearingBarState import com.vitorpamplona.amethyst.commons.ui.layouts.LocalDisappearingScaffoldPadding import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -141,7 +141,7 @@ private fun CodeBrowser( if (openPath != null) { val entry = remember(openFilePath) { snapshot.entryAt(openPath) } - BackHandler { openFilePath = null } + PlatformBackHandler { openFilePath = null } Column(Modifier.fillMaxSize().padding(scaffoldPaddingTop)) { FileHeader(name = openPath.lastOrNull() ?: "", onBack = { openFilePath = null }) HorizontalDivider(thickness = 0.5.dp) @@ -164,7 +164,7 @@ private fun CodeBrowser( var query by rememberSaveable(snapshot.headCommit) { mutableStateOf("") } val searching = query.isNotBlank() - BackHandler(enabled = searching || path.isNotEmpty()) { + PlatformBackHandler(enabled = searching || path.isNotEmpty()) { if (searching) query = "" else pathString = path.dropLast(1).joinToString("/") } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCommitLog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCommitLog.kt index 22b7e59ed2..295232a901 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCommitLog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/code/GitCommitLog.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.code import android.text.format.DateUtils -import androidx.activity.compose.BackHandler import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -66,6 +65,7 @@ import com.vitorpamplona.amethyst.commons.resources.git_repo_code_loading import com.vitorpamplona.amethyst.commons.resources.git_repo_commits import com.vitorpamplona.amethyst.commons.resources.git_repo_file_load_error import com.vitorpamplona.amethyst.commons.resources.git_repo_no_commits +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.commons.ui.note.GitDiffView import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -103,7 +103,7 @@ fun GitCommitLog( if (openCommit != null) { val commit = remember(openCommit, commits) { commits?.firstOrNull { it.oid == openCommit } } - BackHandler { openCommit = null } + PlatformBackHandler { openCommit = null } Column(Modifier.fillMaxSize()) { LogHeader(title = commit?.shortOid ?: "", onBack = { openCommit = null }) HorizontalDivider(thickness = 0.5.dp) @@ -116,7 +116,7 @@ fun GitCommitLog( return } - BackHandler { onBack() } + PlatformBackHandler { onBack() } Column(Modifier.fillMaxSize()) { LogHeader(title = stringRes(Res.string.git_repo_commits), onBack = onBack) HorizontalDivider(thickness = 0.5.dp) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index 0bf885e812..f1469af6f3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home import android.content.Intent import android.net.Uri -import androidx.activity.compose.BackHandler import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable @@ -110,6 +109,7 @@ import com.vitorpamplona.amethyst.commons.resources.schedule_post_always_on_prom import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar import com.vitorpamplona.amethyst.commons.ui.note.BaseUserPicture @@ -275,7 +275,7 @@ internal fun NewPostScreenInner( StrippingFailureDialog(postViewModel.strippingFailureConfirmation) - BackHandler { + PlatformBackHandler { accountViewModel.launchSigner { postViewModel.sendDraftSync() onUiThread { postViewModel.cancel() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyScreen.kt index 6eb9f05f9d..f9b5232f26 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.consumeWindowInsets @@ -40,6 +39,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose import com.vitorpamplona.amethyst.commons.ui.note.types.ReplyRenderType @@ -71,7 +71,7 @@ fun VoiceReplyScreen( viewModel.load(replyToNoteId, recordingFilePath, mimeType, duration, amplitudesJson) } - BackHandler { + PlatformBackHandler { viewModel.cancel() nav.popBack() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/nip75Goals/NewGoalScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/nip75Goals/NewGoalScreen.kt index a8fc08fab7..fe49ecb469 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/nip75Goals/NewGoalScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/nip75Goals/NewGoalScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home.nip75Goals -import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -59,6 +58,7 @@ import com.vitorpamplona.amethyst.commons.resources.goal_summary_placeholder import com.vitorpamplona.amethyst.commons.resources.goal_website_label import com.vitorpamplona.amethyst.commons.resources.goal_website_placeholder import com.vitorpamplona.amethyst.commons.resources.new_goal +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -93,7 +93,7 @@ fun NewGoalScreen( accountViewModel: AccountViewModel, nav: INav, ) { - BackHandler { + PlatformBackHandler { goalViewModel.cancel() nav.popBack() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/activity/NestActivityContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/activity/NestActivityContent.kt index 959c747bce..b0e5bed777 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/activity/NestActivityContent.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/activity/NestActivityContent.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity -import androidx.activity.compose.BackHandler import androidx.activity.compose.LocalActivity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -52,6 +51,7 @@ import com.vitorpamplona.amethyst.commons.resources.nest_loading_room import com.vitorpamplona.amethyst.commons.resources.nest_unjoinable_back import com.vitorpamplona.amethyst.commons.resources.nest_unjoinable_body import com.vitorpamplona.amethyst.commons.resources.nest_unjoinable_title +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -362,7 +362,7 @@ private fun NestActivityBody( // Failed) or PIP isn't supported, we let back fall through to the // default Activity.finish() so the user can cancel out cleanly. val canMinimize = isPipSupported && !isInPipMode && ui.connection is ConnectionUiState.Connected - BackHandler(enabled = canMinimize) { onMinimize() } + PlatformBackHandler(enabled = canMinimize) { onMinimize() } if (isInPipMode) { NestPipScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index 724e63e4c6..c8fbb8787c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages -import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy import androidx.compose.foundation.layout.Column @@ -61,6 +60,7 @@ import com.vitorpamplona.amethyst.commons.resources.messages_new_message_to_capt import com.vitorpamplona.amethyst.commons.resources.public_message import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -142,7 +142,7 @@ fun NewPublicMessageScreen( StrippingFailureDialog(postViewModel.strippingFailureConfirmation) - BackHandler { + PlatformBackHandler { accountViewModel.launchSigner { postViewModel.sendDraftSync() onUiThread { postViewModel.cancel() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt index 8de85d3e54..e969cc41ea 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings -import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -46,7 +45,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.platform.LocalUriHandler import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -62,6 +60,8 @@ import com.vitorpamplona.amethyst.commons.resources.reset_marmot_confirm_title import com.vitorpamplona.amethyst.commons.resources.reset_marmot_failure import com.vitorpamplona.amethyst.commons.resources.reset_marmot_success import com.vitorpamplona.amethyst.commons.resources.settings_search_no_results +import com.vitorpamplona.amethyst.commons.ui.components.rememberLongNotice +import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -98,7 +98,8 @@ fun AllSettingsScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val context = LocalContext.current + val shortNotice = rememberShortNotice() + val longNotice = rememberLongNotice() val uriHandler = LocalUriHandler.current val scope = rememberCoroutineScope() var showResetMarmotDialog by remember { mutableStateOf(false) } @@ -186,13 +187,13 @@ fun AllSettingsScreen( try { accountViewModel.resetMarmotState() launch(Dispatchers.Main) { - Toast.makeText(context, successMessage, Toast.LENGTH_SHORT).show() + shortNotice.show(successMessage) } } catch (e: Exception) { val failureMessage = loadStringRes(Res.string.reset_marmot_failure, e.message ?: "") launch(Dispatchers.Main) { - Toast.makeText(context, failureMessage, Toast.LENGTH_LONG).show() + longNotice.show(failureMessage) } } finally { isResettingMarmot = false diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt index e331aa82c6..da4dd5f1d8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt @@ -80,9 +80,9 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.sats import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import kotlinx.coroutines.launch -import java.text.NumberFormat /** * "Bitcoin" card on the wallet screen, shown above the lightning NWC wallet @@ -277,7 +277,7 @@ private fun BalanceBlock( OnchainBalanceStatus.READY -> { val formatted = remember(sats) { - NumberFormat.getIntegerInstance().format(sats ?: 0L) + formatGrouped(sats ?: 0L) } Text( text = formatted, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt index f926d7fdc5..fb35e33663 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt @@ -51,7 +51,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.text.font.FontFamily @@ -81,13 +80,14 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.note.formatMonthDayTime +import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.formatMonthDayTime import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.datasource.OnchainZapsFilterAssemblerSubscription -import java.text.NumberFormat import kotlin.math.absoluteValue @OptIn(ExperimentalMaterial3Api::class) @@ -309,15 +309,14 @@ private fun OnchainTransactionItem( val amountSats = view.tx.netValueSats.absoluteValue val formattedAmount = remember(view.tx.netValueSats) { - val fmt = NumberFormat.getIntegerInstance() - (if (isIncoming) "+" else "-") + fmt.format(amountSats) + (if (isIncoming) "+" else "-") + formatGrouped(amountSats) } - val context = LocalContext.current + val timeOfDay = rememberTimeOfDayFormatter() val dateText = - remember(view.tx.blockTime, view.tx.confirmations, context) { + remember(view.tx.blockTime, view.tx.confirmations, timeOfDay) { val ts = view.tx.blockTime - if (ts != null) formatMonthDayTime(ts, context) else "" + if (ts != null) formatMonthDayTime(ts, timeOfDay) else "" } val counterpartyPubkeyHex = view.counterpartyPubkeyHex() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt index 7ca59ebe05..ad82a41d08 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt @@ -65,8 +65,8 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_transactions import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import java.text.NumberFormat @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -122,8 +122,7 @@ fun WalletDetailScreen( } else { val formattedBalance = remember(balance) { - val fmt = NumberFormat.getIntegerInstance() - fmt.format(balance ?: 0L) + formatGrouped(balance ?: 0L) } Text( text = formattedBalance, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt index cfee01025f..6289767a30 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt @@ -20,9 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -52,10 +49,11 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign @@ -73,13 +71,15 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_creating_invoice import com.vitorpamplona.amethyst.commons.resources.wallet_description import com.vitorpamplona.amethyst.commons.resources.wallet_receive import com.vitorpamplona.amethyst.commons.resources.wallet_sats +import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.ReceiveState import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import java.text.NumberFormat +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -102,7 +102,8 @@ fun WalletReceiveScreen( val receiveState by walletViewModel.receiveState.collectAsState() var amountText by remember { mutableStateOf("") } var descriptionText by remember { mutableStateOf("") } - val context = LocalContext.current + val clipboard = LocalClipboard.current + val clipboardScope = rememberCoroutineScope() Scaffold( topBar = { @@ -194,8 +195,7 @@ fun WalletReceiveScreen( val formattedAmount = remember(state.amount) { - val fmt = NumberFormat.getIntegerInstance() - fmt.format(state.amount) + formatGrouped(state.amount) } Text( @@ -233,8 +233,7 @@ fun WalletReceiveScreen( ) { OutlinedButton( onClick = { - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - clipboard.setPrimaryClip(ClipData.newPlainText("invoice", state.invoice)) + clipboardScope.launch { clipboard.setText(state.invoice) } }, modifier = Modifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt index 6fe151f908..603addad9d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet -import android.widget.Toast import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -67,7 +66,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.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -103,6 +101,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_rename_title import com.vitorpamplona.amethyst.commons.resources.wallet_sats import com.vitorpamplona.amethyst.commons.resources.wallet_save import com.vitorpamplona.amethyst.commons.resources.wallet_set_default +import com.vitorpamplona.amethyst.commons.ui.components.rememberLongNotice import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.pluralStringRes @@ -110,10 +109,10 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletV import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletInfo import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.experimental.clink.debits.DebitFrequency import kotlinx.coroutines.launch -import java.text.NumberFormat import androidx.compose.material3.Icon as Material3Icon @OptIn(ExperimentalMaterial3Api::class) @@ -248,7 +247,7 @@ private fun MultiWalletHomeContent( cashuMintCount: Int, ) { val walletInfoList by walletViewModel.walletInfoList.collectAsState() - val context = LocalContext.current + val longNotice = rememberLongNotice() val budgetApprovedMsg = stringRes(Res.string.clink_budget_approved) val debitNoResponseMsg = stringRes(Res.string.clink_debit_no_response) @@ -298,7 +297,7 @@ private fun MultiWalletHomeContent( !error.isNullOrBlank() -> error else -> debitNoResponseMsg } - Toast.makeText(context, msg, Toast.LENGTH_LONG).show() + longNotice.show(msg) } } } else { @@ -460,8 +459,7 @@ private fun WalletCard( Column(horizontalAlignment = Alignment.End) { val formattedBalance = remember(walletInfo.balanceSats) { - val fmt = NumberFormat.getIntegerInstance() - fmt.format(walletInfo.balanceSats ?: 0L) + formatGrouped(walletInfo.balanceSats ?: 0L) } Text( text = formattedBalance, @@ -574,7 +572,7 @@ private fun CashuWalletRow( onClick: () -> Unit, ) { val formattedBalance = - remember(balanceSats) { NumberFormat.getIntegerInstance().format(balanceSats) } + remember(balanceSats) { formatGrouped(balanceSats) } Card( modifier = Modifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt index 0c35d86bdc..735b458de5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt @@ -20,8 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet -import android.content.ClipboardManager -import android.content.Context import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -47,10 +45,11 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -63,11 +62,13 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_pay import com.vitorpamplona.amethyst.commons.resources.wallet_payment_sending import com.vitorpamplona.amethyst.commons.resources.wallet_payment_success import com.vitorpamplona.amethyst.commons.resources.wallet_send +import com.vitorpamplona.amethyst.commons.ui.components.util.getText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.SendState import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel +import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -89,7 +90,8 @@ fun WalletSendScreen( val sendState by walletViewModel.sendState.collectAsState() var invoiceText by remember { mutableStateOf("") } - val context = LocalContext.current + val clipboard = LocalClipboard.current + val clipboardScope = rememberCoroutineScope() Scaffold( topBar = { @@ -128,11 +130,7 @@ fun WalletSendScreen( maxLines = 5, trailingIcon = { IconButton(onClick = { - val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = clipboard.primaryClip - if (clip != null && clip.itemCount > 0) { - invoiceText = clip.getItemAt(0).text?.toString() ?: "" - } + clipboardScope.launch { invoiceText = clipboard.getText() ?: "" } }) { Icon( symbol = MaterialSymbols.ContentPaste, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt index 4cd03b8a5b..95fb71e0c4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt @@ -51,7 +51,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -78,14 +77,15 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay +import com.vitorpamplona.amethyst.commons.ui.note.formatMonthDayTime +import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.note.formatMonthDayTime import com.vitorpamplona.quartz.nip47WalletConnect.rpc.NwcTransaction import com.vitorpamplona.quartz.nip47WalletConnect.rpc.NwcTransactionType -import java.text.NumberFormat @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -278,14 +278,13 @@ private fun TransactionItem( val amountSats = (tx.amount ?: 0L) / 1000L val formattedAmount = remember(amountSats) { - val fmt = NumberFormat.getIntegerInstance() - (if (isIncoming) "+" else "-") + fmt.format(amountSats) + (if (isIncoming) "+" else "-") + formatGrouped(amountSats) } - val context = LocalContext.current + val timeOfDay = rememberTimeOfDayFormatter() val dateText = - remember(tx.created_at, context) { - tx.created_at?.let { formatMonthDayTime(it, context) } ?: "" + remember(tx.created_at, timeOfDay) { + tx.created_at?.let { formatMonthDayTime(it, timeOfDay) } ?: "" } val directionLabel = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt index 8f3bc76fa8..bbf929d1b3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt @@ -101,8 +101,8 @@ import com.vitorpamplona.amethyst.commons.resources.cashu_wizard_title import com.vitorpamplona.amethyst.commons.resources.cashu_wizard_use_wallet import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.stringRes +import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import java.text.NumberFormat /** * Find-or-create wizard for the Cashu wallet. Reached from the wallet screen's @@ -227,7 +227,7 @@ fun CashuWalletWizardScreen( } } -private fun formatSats(sats: Long): String = NumberFormat.getInstance().format(sats) +private fun formatSats(sats: Long): String = formatGrouped(sats) /** * Animate a sats figure counting up from zero the first time it's shown, then diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutScreen.kt index 8a168763c0..211417c48e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/workouts/NewWorkoutScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts -import androidx.activity.compose.BackHandler import androidx.compose.animation.Crossfade import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -70,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.resources.workout_minutes import com.vitorpamplona.amethyst.commons.resources.workout_notes import com.vitorpamplona.amethyst.commons.resources.workout_seconds import com.vitorpamplona.amethyst.commons.resources.workout_title +import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -91,7 +91,7 @@ fun NewWorkoutScreen( postViewModel.init(accountViewModel) postViewModel.prefill(prefill) - BackHandler { + PlatformBackHandler { postViewModel.cancel() nav.popBack() } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt index be7628aff7..f0885e8207 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/model/cache/LocalCacheHost.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.model.cache import com.vitorpamplona.amethyst.commons.relays.nip11RelayInfo.Nip11CachedRetriever import com.vitorpamplona.amethyst.commons.relays.nip11RelayInfo.Nip11Fetcher +import com.vitorpamplona.amethyst.commons.service.UsageCounter import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @@ -81,6 +82,9 @@ interface LocalCacheHost { */ fun relaySelfPubKey(relay: NormalizedRelayUrl): HexKey? = null + /** The resource-usage ledger's counters; counts nothing by default. */ + val usageCounter: UsageCounter get() = UsageCounter.None + /** * The relay's NIP-11 document as far as the shell has it in memory. Never fetches: `null` * (or a document with nothing in it) means "not loaded yet", which callers must treat as diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/UsageCounter.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/UsageCounter.kt new file mode 100644 index 0000000000..5e6118e8d1 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/UsageCounter.kt @@ -0,0 +1,34 @@ +/* + * 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 + +/** Adds to a diagnostics counter in the app's resource-usage ledger. Never throws. */ +fun interface UsageCounter { + fun add( + key: String, + amount: Long, + ) + + companion object { + /** Counts nothing: hosts without a ledger. */ + val None = UsageCounter { _, _ -> } + } +} diff --git a/commonsUI/build.gradle.kts b/commonsUI/build.gradle.kts index 1ff1db16a9..53b1c20463 100644 --- a/commonsUI/build.gradle.kts +++ b/commonsUI/build.gradle.kts @@ -212,6 +212,8 @@ kotlin { implementation(libs.androidx.ui.tooling.preview) // WindowInsetsControllerCompat, for DisappearingScaffold's immersive status bar. implementation(libs.androidx.core.ktx) + // BackHandler, behind the shared PlatformBackHandler. Apache-2.0, already in the app. + implementation(libs.androidx.activity.compose) } } @@ -229,6 +231,11 @@ kotlin { val skikoMain = create("skikoMain") { dependsOn(commonMain.get()) + dependencies { + // BackHandler for desktop (Esc) and iOS (swipe), behind PlatformBackHandler. + // JetBrains Compose, Apache-2.0; Android uses AndroidX's instead. + implementation(libs.jetbrains.compose.ui.backhandler) + } } getByName("jvmMain").dependsOn(skikoMain) iosMain.dependsOn(skikoMain) diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.android.kt new file mode 100644 index 0000000000..fbd0460a54 --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.android.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.amethyst.commons.ui.components + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable + +@Composable +actual fun PlatformBackHandler( + enabled: Boolean, + onBack: () -> Unit, +) = BackHandler(enabled, onBack) diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.android.kt index 24d16cd38a..994d6332c0 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.android.kt @@ -30,3 +30,9 @@ actual fun rememberShortNotice(): ShortNotice { val context = LocalContext.current return remember(context) { ShortNotice { message -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show() } } } + +@Composable +actual fun rememberLongNotice(): ShortNotice { + val context = LocalContext.current + return remember(context) { ShortNotice { message -> Toast.makeText(context, message, Toast.LENGTH_LONG).show() } } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.kt new file mode 100644 index 0000000000..60225f29ed --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.kt @@ -0,0 +1,33 @@ +/* + * 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.ui.components + +import androidx.compose.runtime.Composable + +/** + * Intercepts the system back gesture/button while [enabled]: AndroidX's `BackHandler` on + * Android, Compose Multiplatform's on desktop (Esc) and iOS (swipe back). + */ +@Composable +expect fun PlatformBackHandler( + enabled: Boolean = true, + onBack: () -> Unit, +) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.kt index afcf42777d..8a5766f8bd 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.kt @@ -33,3 +33,7 @@ fun interface ShortNotice { */ @Composable expect fun rememberShortNotice(): ShortNotice + +/** Like [rememberShortNotice], shown longer: for errors the reader needs time to take in. */ +@Composable +expect fun rememberLongNotice(): ShortNotice diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt new file mode 100644 index 0000000000..313eb57144 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt @@ -0,0 +1,35 @@ +/* + * 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.ui.note + +private val monthDay = DateSkeletonFormatter("MMMd") + +/** + * Locale-ordered month and day followed by [timeOfDay] ("May 28 14:32" / "28 May 2:32 PM"). Pass + * [rememberTimeOfDayFormatter] so the clock follows the system 12/24-hour setting. + */ +fun formatMonthDayTime( + epochSeconds: Long, + timeOfDay: (epochMillis: Long) -> String, +): String { + val millis = epochSeconds * 1000L + return "${monthDay.format(millis)} ${timeOfDay(millis)}" +} diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.ios.kt index a991b09cfb..b0d73e3abf 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.ios.kt +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.ios.kt @@ -27,3 +27,6 @@ private val loggedNotice = ShortNotice { message -> Log.d("ShortNotice") { messa @Composable actual fun rememberShortNotice(): ShortNotice = loggedNotice + +@Composable +actual fun rememberLongNotice(): ShortNotice = loggedNotice diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.jvm.kt index a991b09cfb..b0d73e3abf 100644 --- a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.jvm.kt +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ShortNotice.jvm.kt @@ -27,3 +27,6 @@ private val loggedNotice = ShortNotice { message -> Log.d("ShortNotice") { messa @Composable actual fun rememberShortNotice(): ShortNotice = loggedNotice + +@Composable +actual fun rememberLongNotice(): ShortNotice = loggedNotice diff --git a/commonsUI/src/skikoMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.skiko.kt b/commonsUI/src/skikoMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.skiko.kt new file mode 100644 index 0000000000..70ba995c8b --- /dev/null +++ b/commonsUI/src/skikoMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/PlatformBackHandler.skiko.kt @@ -0,0 +1,32 @@ +/* + * 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.ui.components + +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.backhandler.BackHandler + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +actual fun PlatformBackHandler( + enabled: Boolean, + onBack: () -> Unit, +) = BackHandler(enabled, onBack) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 13297f858e..45b096ea6b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -197,6 +197,7 @@ jetbrains-compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", jetbrains-compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "jetbrainsCompose" } jetbrains-compose-ui-tooling = { module = "org.jetbrains.compose.ui:ui-tooling", version.ref = "jetbrainsCompose" } jetbrains-compose-ui-tooling-preview = { module = "org.jetbrains.compose.ui:ui-tooling-preview", version.ref = "jetbrainsCompose" } +jetbrains-compose-ui-backhandler = { module = "org.jetbrains.compose.ui:ui-backhandler", version.ref = "jetbrainsCompose" } jetbrains-compose-ui-test-junit4 = { module = "org.jetbrains.compose.ui:ui-test-junit4", version.ref = "jetbrainsCompose" } google-mlkit-genai-proofreading = { group = "com.google.mlkit", name = "genai-proofreading", version.ref = "genaiProofreading" } google-mlkit-genai-prompt = { group = "com.google.mlkit", name = "genai-prompt", version.ref = "genaiPrompt" } From 89e3f4a779679a2b8f6b54fc7f15c428c2b53746 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 07:12:16 +0000 Subject: [PATCH 07/20] refactor: move 11 more screens into commonsUI (step 6 wave 5) The wallet detail/send/receive/transaction screens, on-chain transactions, the Buzz canvas, agent console and DM list, badge awarding, old bookmarks and the settings index move with their closures (23 files). The legal settings section is per flavour (Play links the hosted policies, F-Droid shows none), so it becomes AppPlatform.legalSettingsCategory and buildSettingsCatalog takes the result. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../com/vitorpamplona/amethyst/AppModules.kt | 2 +- .../RoleBasedHttpClientBuilder.kt | 2 +- .../calendar/CalendarReminderWorker.kt | 2 +- .../NotificationCatchUpWorker.kt | 2 +- .../playerPool/MediaPlayTimeTracker.kt | 2 +- .../relayClient/RelayProxyClientConnector.kt | 2 +- .../resourceusage/BatteryDrainSampler.kt | 2 ++ .../resourceusage/ForegroundTimeIntegrator.kt | 1 + .../resourceusage/MeteringNostrSigner.kt | 1 + .../resourceusage/ProcessCpuSampler.kt | 1 + .../RelayConnectionTimeIntegrator.kt | 1 + .../resourceusage/RelayUsageListener.kt | 1 + .../resourceusage/ScreenTimeIntegrator.kt | 1 + .../resourceusage/UsageCountingInterceptor.kt | 1 + .../service/resourceusage/UsageSummary.kt | 3 ++- .../scheduledposts/ScheduledPostWorker.kt | 2 +- .../amethyst/ui/navigation/AppNavigation.kt | 22 +++++++++---------- .../ui/platform/AndroidAppPlatform.kt | 6 +++++ .../loggedIn/buzz/BuzzRelayImportViewModel.kt | 1 + .../relayGroup/RelayGroupChannelListScreen.kt | 4 ++-- .../resourceusage/ResourceUsageLedgerTest.kt | 1 + .../2026-09-27-one-ui-android-desktop.md | 15 ++++++++++++- .../service/resourceusage/UsageKeys.kt | 2 +- .../commons/ui/platform/AppPlatform.kt | 5 +++++ .../loggedIn/badges/award/AwardBadgeScreen.kt | 6 ++--- .../badges/award/AwardBadgeViewModel.kt | 2 +- .../old/OldBookmarkListScreen.kt | 12 +++++----- .../old/dal/OldBookmarkPrivateFeedFilter.kt | 2 +- .../dal/OldBookmarkPrivateFeedViewModel.kt | 9 ++++++-- .../old/dal/OldBookmarkPublicFeedFilter.kt | 2 +- .../old/dal/OldBookmarkPublicFeedViewModel.kt | 9 ++++++-- .../loggedIn/buzz/AgentConsoleScreen.kt | 6 ++--- .../loggedIn/buzz/AgentConsoleViewModel.kt | 4 +++- .../screen/loggedIn/buzz/BuzzCanvasScreen.kt | 3 ++- .../screen/loggedIn/buzz/BuzzDmListScreen.kt | 6 ++--- .../loggedIn/buzz/BuzzDmListViewModel.kt | 4 +++- .../screen/loggedIn/buzz/BuzzJoinReconnect.kt | 6 ++--- .../loggedIn/settings/AllSettingsScreen.kt | 8 +++++-- .../settings/SettingsCatalogBuilder.kt | 5 +++-- .../wallet/OnchainTransactionsScreen.kt | 8 +++---- .../wallet/OnchainTransactionsViewModel.kt | 3 ++- .../loggedIn/wallet/WalletDetailScreen.kt | 6 ++--- .../loggedIn/wallet/WalletReceiveScreen.kt | 6 ++--- .../loggedIn/wallet/WalletSendScreen.kt | 6 ++--- .../wallet/WalletTransactionsScreen.kt | 6 ++--- .../OnchainZapsFilterAssemblerSubscription.kt | 2 +- 46 files changed, 130 insertions(+), 73 deletions(-) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/service/resourceusage/UsageKeys.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/award/AwardBadgeViewModel.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedFilter.kt (94%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedViewModel.kt (82%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedFilter.kt (94%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedViewModel.kt (82%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/BuzzDmListScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt (93%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/AllSettingsScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/WalletDetailScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/WalletSendScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/datasource/OnchainZapsFilterAssemblerSubscription.kt (96%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt index 0e857e8564..b7d064b07d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt @@ -79,6 +79,7 @@ import com.vitorpamplona.amethyst.commons.service.lnurl.OkHttpLnurlEndpointResol import com.vitorpamplona.amethyst.commons.service.pow.PoWJobStore import com.vitorpamplona.amethyst.commons.service.pow.PoWPolicy import com.vitorpamplona.amethyst.commons.service.pow.PoWPublishQueue +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.amethyst.commons.state.UiSettingsState import com.vitorpamplona.amethyst.commons.tor.TorRelayState import com.vitorpamplona.amethyst.commons.tor.TorSettings @@ -135,7 +136,6 @@ import com.vitorpamplona.amethyst.service.resourceusage.ResourceUsageStore import com.vitorpamplona.amethyst.service.resourceusage.ScreenTimeIntegrator import com.vitorpamplona.amethyst.service.resourceusage.SessionTimeIntegrator import com.vitorpamplona.amethyst.service.resourceusage.UsageCountingInterceptor -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys import com.vitorpamplona.amethyst.service.safeCacheDir import com.vitorpamplona.amethyst.service.scheduledposts.ScheduledPostWorker import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomMirrorQueue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/privacyOptions/RoleBasedHttpClientBuilder.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/privacyOptions/RoleBasedHttpClientBuilder.kt index 278443ed5e..69d943517d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/privacyOptions/RoleBasedHttpClientBuilder.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/privacyOptions/RoleBasedHttpClientBuilder.kt @@ -23,10 +23,10 @@ package com.vitorpamplona.amethyst.model.privacyOptions import com.vitorpamplona.amethyst.commons.service.http.DualHttpClientManager import com.vitorpamplona.amethyst.commons.service.http.IRoleBasedHttpClientBuilder import com.vitorpamplona.amethyst.commons.service.http.ProxiedSocketFactory +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow import com.vitorpamplona.amethyst.commons.tor.TorType import com.vitorpamplona.amethyst.service.resourceusage.HttpUsageMeter -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import okhttp3.OkHttpClient import java.net.InetSocketAddress diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/calendar/CalendarReminderWorker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/calendar/CalendarReminderWorker.kt index b3f34ae3ce..06dab13025 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/calendar/CalendarReminderWorker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/calendar/CalendarReminderWorker.kt @@ -32,9 +32,9 @@ import com.vitorpamplona.amethyst.commons.model.nip52Calendar.appointmentView import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.calendar_reminder_body import com.vitorpamplona.amethyst.commons.resources.calendar_reminder_default_title +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.amethyst.commons.ui.loadPluralStringRes import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.nip52Calendar.appt.day.CalendarDateSlotEvent import com.vitorpamplona.quartz.nip52Calendar.appt.tags.RSVPStatusTag import com.vitorpamplona.quartz.nip52Calendar.appt.time.CalendarTimeSlotEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationCatchUpWorker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationCatchUpWorker.kt index d11a88bd00..ed54f71552 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationCatchUpWorker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/notifications/NotificationCatchUpWorker.kt @@ -31,7 +31,7 @@ import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.delay import kotlinx.coroutines.flow.first diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaPlayTimeTracker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaPlayTimeTracker.kt index 44acb2d203..17e72de3c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaPlayTimeTracker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/playerPool/MediaPlayTimeTracker.kt @@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.service.playback.playerPool import android.os.SystemClock import androidx.media3.common.Player import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys /** * Accounts actual media playback time into the resource-usage ledger. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt index 583650c1f1..a3efa62313 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/RelayProxyClientConnector.kt @@ -21,10 +21,10 @@ package com.vitorpamplona.amethyst.service.relayClient import com.vitorpamplona.amethyst.commons.service.connectivity.ConnectivityStatus +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.amethyst.commons.tor.RelayClassification import com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation import com.vitorpamplona.amethyst.commons.tor.TorRelaySettings -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys import com.vitorpamplona.amethyst.ui.tor.TorServiceStatus import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.utils.Log diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/BatteryDrainSampler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/BatteryDrainSampler.kt index 5487055071..f847ac2acc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/BatteryDrainSampler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/BatteryDrainSampler.kt @@ -20,6 +20,8 @@ */ package com.vitorpamplona.amethyst.service.resourceusage +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys + /** * Samples the device battery level at each ledger flush and accumulates the * drops that happen while discharging into [UsageKeys.BATTERY_DRAIN_FG] / diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ForegroundTimeIntegrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ForegroundTimeIntegrator.kt index 0e1bb3200c..c90b828f06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ForegroundTimeIntegrator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ForegroundTimeIntegrator.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.service.resourceusage import android.os.SystemClock +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/MeteringNostrSigner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/MeteringNostrSigner.kt index 8bfe2fe129..731d9a0532 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/MeteringNostrSigner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/MeteringNostrSigner.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.resourceusage +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ProcessCpuSampler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ProcessCpuSampler.kt index 408101afa4..2c306bfb21 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ProcessCpuSampler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ProcessCpuSampler.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.service.resourceusage import android.os.Process +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys /** * Samples whole-process CPU time (user+system, [Process.getElapsedCpuTime]) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayConnectionTimeIntegrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayConnectionTimeIntegrator.kt index b3afc9c6e5..23ebc58de7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayConnectionTimeIntegrator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayConnectionTimeIntegrator.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.service.resourceusage import android.os.SystemClock +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayUsageListener.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayUsageListener.kt index 5abc1998e0..2b27b83e39 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayUsageListener.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/RelayUsageListener.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.resourceusage import android.os.SystemClock import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.purposeOrNull +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.ClosedMessage diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ScreenTimeIntegrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ScreenTimeIntegrator.kt index b995054ace..b072bb1d72 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ScreenTimeIntegrator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/ScreenTimeIntegrator.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.service.resourceusage import android.os.SystemClock +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageCountingInterceptor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageCountingInterceptor.kt index 2b2f9ea2cd..c1d3922967 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageCountingInterceptor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageCountingInterceptor.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.service.resourceusage import android.os.SystemClock +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageSummary.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageSummary.kt index 1fd24f085d..a00463ad45 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageSummary.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageSummary.kt @@ -20,7 +20,8 @@ */ package com.vitorpamplona.amethyst.service.resourceusage -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys.sumMatching +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys.sumMatching /** * Headline metrics derived from one or more daily counter buckets. Shared by diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/scheduledposts/ScheduledPostWorker.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/scheduledposts/ScheduledPostWorker.kt index aa2de5fc11..c34ca38dba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/scheduledposts/ScheduledPostWorker.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/scheduledposts/ScheduledPostWorker.kt @@ -33,7 +33,7 @@ import androidx.work.WorkerParameters import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostPublisher import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStatus -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.publishAndConfirmDetailed import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl 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 a3ad111652..db2db89973 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 @@ -65,17 +65,22 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.TabReselectCo import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.apps.recommendations.ProfileAppRecommendationsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BackupConflictReviewScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.award.AwardBadgeScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.profile.ProfileBadgesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.default.BookmarkListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.list.ListOfBookmarkGroupsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.membershipManagement.ArticleBookmarkListManagementScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.membershipManagement.PostBookmarkListManagementScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old.OldBookmarkListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.podcasts.BookmarkedPodcastsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.repositories.BookmarkedRepositoriesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.browser.BrowserScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentAttestationScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentConsoleScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentPersonaEditScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentWorkBoardScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzCanvasScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzDmListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzForumPostScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzInviteScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzNewDmScreen @@ -136,6 +141,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.RelayInforma import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.eventsync.EventSyncScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.nip43.RelayMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.vanish.VanishEventsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.AllSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BlockedUsersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CallSettingsScreen @@ -162,8 +168,13 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuMintRec import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuMintsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.CashuWalletSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.OnchainTransactionsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.ReloadMintScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TopUpMintScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletDetailScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletReceiveScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletSendScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletTransactionsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.WorkoutsScreen import com.vitorpamplona.amethyst.commons.ui.uriToRoute import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -199,14 +210,9 @@ import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.ArticlesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.BadgesScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.award.AwardBadgeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.display.BookmarkGroupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.metadata.BookmarkGroupMetadataScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.OldBookmarkListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.WebAppScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.AgentConsoleScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzCanvasScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzDmListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzForumThreadScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarCollectionsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.CalendarReminderSettingsScreen @@ -310,7 +316,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.subscriptions.Active import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.vanish.RequestToVanishScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.scheduledposts.ScheduledPostsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AllSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AudioVisualizerSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ComposeSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen @@ -331,12 +336,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.VideoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.hls.NewHlsVideoScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainTransactionsScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletDetailScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletReceiveScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletSendScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletTransactionsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.wizard.CashuWalletWizardScreen 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/platform/AndroidAppPlatform.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/platform/AndroidAppPlatform.kt index 93fd11d3d5..468d992788 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 @@ -25,6 +25,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.UriHandler import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.model.navigation.Route @@ -32,6 +33,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppLauncher import com.vitorpamplona.amethyst.commons.ui.platform.AppPlatform 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.amethyst.favorites.FavoriteAppLauncher import com.vitorpamplona.amethyst.ui.navigation.topbars.AndroidAroundMeLocationLabel @@ -44,11 +46,15 @@ import com.vitorpamplona.amethyst.ui.note.creators.location.GeohashLocationPicke import com.vitorpamplona.amethyst.ui.note.creators.location.GeohashLocationPickerDialog as AppGeohashLocationPickerDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.rememberConcordImageModel as AppRememberConcordImageModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.scanner.QrCodeScannerDialog as AppQrCodeScannerDialog +import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.legalSettingsCategory as flavorLegalSettingsCategory /** Android's [AppPlatform]: the app's own shell pieces, camera scanner, app launcher and icon caches. */ object AndroidAppPlatform : AppPlatform { override val isCastingAvailable: Boolean get() = BuildConfig.IS_CASTING_AVAILABLE + // Per flavour: Play links the hosted policies, F-Droid surfaces none. + override fun legalSettingsCategory(uriHandler: UriHandler): SettingsCategory? = flavorLegalSettingsCategory(uriHandler) + @Composable override fun AppBottomBar( selectedRoute: Route?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzRelayImportViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzRelayImportViewModel.kt index 0a7f5e0893..524539c2b7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzRelayImportViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzRelayImportViewModel.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupDeletions import com.vitorpamplona.amethyst.commons.relayClient.channel.relayGroup.RELAY_GROUP_METADATA_KINDS import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.reconnectPoolAfterJoin import com.vitorpamplona.quartz.buzz.notifications.MemberAddedNotificationEvent import com.vitorpamplona.quartz.buzz.stream.SystemMessageEvent import com.vitorpamplona.quartz.buzz.workspace.isBuzzDm diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupChannelListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupChannelListScreen.kt index fcc4e5b605..324fd1b444 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupChannelListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupChannelListScreen.kt @@ -109,17 +109,17 @@ import com.vitorpamplona.amethyst.commons.ui.note.timeAgoShort import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.pluralStringRes import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.BuzzDmListViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.HiddenDmHeader import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupCardWarmupSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.warningColor import com.vitorpamplona.amethyst.commons.util.sortedBySnapshot import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzAddPeopleDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzDmListViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzImportRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzRelayImportViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.BuzzWorkspaceOverflowMenu -import com.vitorpamplona.amethyst.ui.screen.loggedIn.buzz.HiddenDmHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.types.buzzTimelinePreviewSummary import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupsOnRelaySubscription import com.vitorpamplona.quartz.buzz.workspace.BUZZ_CHANNEL_TYPE_DM diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/resourceusage/ResourceUsageLedgerTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/resourceusage/ResourceUsageLedgerTest.kt index 6f0365319d..93055997bf 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/resourceusage/ResourceUsageLedgerTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/resourceusage/ResourceUsageLedgerTest.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.service.resourceusage import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.amethyst.service.playback.playerPool.MediaPlayTimeTracker import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey 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 f12edcbbf0..2ea3bfda8f 100644 --- a/commons/plans/2026-09-27-one-ui-android-desktop.md +++ b/commons/plans/2026-09-27-one-ui-android-desktop.md @@ -390,9 +390,22 @@ assemblers, `EventSync`, …). Packages are renamed on the way: link and coordinator screens; relay auth, privacy, home tabs, security filters, profile UI, drawer and video-player settings; Cashu wallet, NWC and CLINK setup; the Concord channel list and invite links; the attestation screen and the redirect loader. + - **Wave 5 cuts:** + - `PlatformBackHandler` wraps AndroidX's `BackHandler` on Android and Compose + Multiplatform's `ui-backhandler` on desktop and iOS. + - `rememberLongNotice` joins `rememberShortNotice`. + - The wallet screens use `formatGrouped` and the shared clipboard extensions. + - A shared `formatMonthDayTime` builds its label from `DateSkeletonFormatter` and the + time-of-day formatter. + - `LocalCacheHost.usageCounter` feeds the resource-usage ledger. + - The flavour-specific legal settings section is `AppPlatform.legalSettingsCategory`. + - **Wave 5 moved:** 11 screens, 23 files: the wallet detail, send, receive and + transaction screens; on-chain transactions; the Buzz canvas, agent console and DM + list; badge awarding; old bookmarks; and the settings index. - **Next:** re-measure. Known exits: the upload/gallery stack, the Android-only `AccountViewModel` actions, `CalendarTimeFormat`, the chat views, `NestUiState`, - `NappletLauncher`, `UpdateZapAmountDialog` (biometrics), and the `java.time` pickers. + `NappletLauncher`, `UpdateZapAmountDialog` (biometrics), the `java.time` pickers, and + the language list in user settings. 7. **Navigation**: the library swap, then `AppNavigation` + rail + drawer + bottom bar. 8. **The app root port** and the new JVM shim. Then the Desktop feature inventory, and retiring the old `desktopApp`. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageKeys.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/resourceusage/UsageKeys.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageKeys.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/resourceusage/UsageKeys.kt index 179dc5e929..5fb71aac2e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/resourceusage/UsageKeys.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/resourceusage/UsageKeys.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.resourceusage +package com.vitorpamplona.amethyst.commons.service.resourceusage import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose import com.vitorpamplona.quartz.nip01Core.relay.client.single.basic.BasicRelayClient 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 99b7ebdf33..830d968259 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 @@ -24,10 +24,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.UriHandler import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp import com.vitorpamplona.amethyst.commons.model.navigation.Route import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav 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 @@ -42,6 +44,9 @@ interface AppPlatform { /** Whether this build can cast media to a TV (the Play flavour's Cast SDK). */ val isCastingAvailable: Boolean get() = false + /** The settings screen's legal links, when the build's distribution calls for them. */ + fun legalSettingsCategory(uriHandler: UriHandler): SettingsCategory? = null + @Composable fun AppBottomBar( selectedRoute: Route?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt index 8c399774c1..7562ec5a8c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/award/AwardBadgeScreen.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.badges.award +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.award import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -46,7 +46,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.award_badge @@ -56,6 +55,7 @@ import com.vitorpamplona.amethyst.commons.resources.award_badge_search_label import com.vitorpamplona.amethyst.commons.resources.award_badge_search_placeholder import com.vitorpamplona.amethyst.commons.ui.components.Nip05OrPubkeyLine import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar @@ -75,7 +75,7 @@ fun AwardBadgeScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val vm: AwardBadgeViewModel = viewModel() + val vm: AwardBadgeViewModel = rememberViewModel { AwardBadgeViewModel() } LaunchedEffect(accountViewModel, kind, pubKeyHex, dTag) { vm.init(accountViewModel, kind, pubKeyHex, dTag) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/award/AwardBadgeViewModel.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/award/AwardBadgeViewModel.kt index 4b118984b9..c4dee892d1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/award/AwardBadgeViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/award/AwardBadgeViewModel.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.badges.award +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.award import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt index 28acf3594d..c9b8137a4a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/OldBookmarkListScreen.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.bookmarkgroups.old +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box @@ -44,7 +44,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -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.AddressableNote @@ -57,17 +56,18 @@ import com.vitorpamplona.amethyst.commons.resources.private_bookmarks import com.vitorpamplona.amethyst.commons.resources.public_bookmarks import com.vitorpamplona.amethyst.commons.ui.components.DeletedItemsBanner import com.vitorpamplona.amethyst.commons.ui.components.rememberShortNotice +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old.dal.OldBookmarkPrivateFeedViewModel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old.dal.OldBookmarkPublicFeedViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.TabRowHeight import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.dal.OldBookmarkPrivateFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.old.dal.OldBookmarkPublicFeedViewModel import kotlinx.coroutines.launch @Composable @@ -76,13 +76,13 @@ fun OldBookmarkListScreen( nav: INav, ) { val publicFeedViewModel: OldBookmarkPublicFeedViewModel = - viewModel( + rememberViewModel( key = "NostrOldBookmarkPublicFeedViewModel", factory = OldBookmarkPublicFeedViewModel.Factory(accountViewModel.account), ) val privateFeedViewModel: OldBookmarkPrivateFeedViewModel = - viewModel( + rememberViewModel( key = "NostrOldBookmarkPrivateFeedViewModel", factory = OldBookmarkPrivateFeedViewModel.Factory(accountViewModel.account), ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedFilter.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedFilter.kt index 10732d7ac2..3483f70a41 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedFilter.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.bookmarkgroups.old.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old.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/bookmarkgroups/old/dal/OldBookmarkPrivateFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedViewModel.kt similarity index 82% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedViewModel.kt index 21702425ae..7487023101 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPrivateFeedViewModel.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.bookmarkgroups.old.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old.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.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class OldBookmarkPrivateFeedViewModel( @@ -34,6 +36,9 @@ class OldBookmarkPrivateFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = OldBookmarkPrivateFeedViewModel(account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = OldBookmarkPrivateFeedViewModel(account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedFilter.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedFilter.kt index cec82a37bb..865cfe8cfb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedFilter.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.bookmarkgroups.old.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old.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/bookmarkgroups/old/dal/OldBookmarkPublicFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedViewModel.kt similarity index 82% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedViewModel.kt index e0093152d9..7e63622092 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/bookmarkgroups/old/dal/OldBookmarkPublicFeedViewModel.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.bookmarkgroups.old.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.old.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.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class OldBookmarkPublicFeedViewModel( @@ -34,6 +36,9 @@ class OldBookmarkPublicFeedViewModel( val account: Account, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = OldBookmarkPublicFeedViewModel(account) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = OldBookmarkPublicFeedViewModel(account) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt index 7b0b5b9711..e2110fda0e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentConsoleScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -55,13 +55,13 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -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.buzz.AgentFleetMetrics import com.vitorpamplona.amethyst.commons.model.buzz.AgentUsageSummary import com.vitorpamplona.amethyst.commons.model.buzz.TokenTotals import com.vitorpamplona.amethyst.commons.model.navigation.Route +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz.AgentAttestationScreen @@ -87,7 +87,7 @@ fun AgentConsoleScreen( nav: INav, ) { val pubkey = accountViewModel.account.userProfile().pubkeyHex - val viewModel: AgentConsoleViewModel = viewModel(key = "AgentConsole-$relayUrl-$pubkey") + val viewModel: AgentConsoleViewModel = rememberViewModel(key = "AgentConsole-$relayUrl-$pubkey") { AgentConsoleViewModel() } viewModel.bind(accountViewModel.account, relayUrl) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt index 73e28e912e..2a3249f90f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/AgentConsoleViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/AgentConsoleViewModel.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel @@ -43,6 +43,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.utils.concurrent.ConcurrentSet import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -51,6 +52,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlin.concurrent.Volatile /** * Backing ViewModel for the [AgentConsoleScreen] — the workspace owner's read-only diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt index 88b62bc0ac..ce7eeeb538 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzCanvasScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzCanvasScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -73,6 +73,7 @@ import com.vitorpamplona.quartz.buzz.workspace.isBuzzDm import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip29RelayGroups.GroupId import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListScreen.kt index 5267f16fa3..08e52e8dc4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListScreen.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -62,7 +62,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.User @@ -84,6 +83,7 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_dm_new import com.vitorpamplona.amethyst.commons.resources.buzz_dm_title import com.vitorpamplona.amethyst.commons.resources.cancel import com.vitorpamplona.amethyst.commons.resources.remove_from_messages +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel 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.TimeAgoLabels @@ -119,7 +119,7 @@ fun BuzzDmListScreen( nav: INav, ) { val pubkey = accountViewModel.account.userProfile().pubkeyHex - val viewModel: BuzzDmListViewModel = viewModel(key = "BuzzDmList-$relayUrl-$pubkey") + val viewModel: BuzzDmListViewModel = rememberViewModel(key = "BuzzDmList-$relayUrl-$pubkey") { BuzzDmListViewModel() } viewModel.bind(accountViewModel.account, relayUrl) val rows by viewModel.rows.collectAsStateWithLifecycle() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt index 18052af7cf..de520c976d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListViewModel.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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel @@ -48,6 +48,7 @@ import com.vitorpamplona.quartz.nip29RelayGroups.GroupId import com.vitorpamplona.quartz.nipC7Chats.ChatEvent import com.vitorpamplona.quartz.utils.concurrent.ConcurrentMap import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -56,6 +57,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlin.concurrent.Volatile /** * Backing ViewModel for [BuzzDmListScreen] — the user's Buzz direct-message inbox. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt index feadf3604a..a2cea31f96 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzJoinReconnect.kt @@ -18,10 +18,10 @@ * 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.buzz +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.buzz import com.vitorpamplona.amethyst.commons.model.cache.LocalCache -import com.vitorpamplona.amethyst.service.resourceusage.UsageKeys +import com.vitorpamplona.amethyst.commons.service.resourceusage.UsageKeys import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient /** @@ -44,7 +44,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient * reconnect — it hands off to the in-app browser rather than reading a `#p=me`-gated * subscription — so `relay.trigger.buzz` undercounts joins, not re-challenges. */ -internal fun reconnectPoolAfterJoin(client: INostrClient) { +fun reconnectPoolAfterJoin(client: INostrClient) { LocalCache.appHost.usageCounter.add(UsageKeys.relayTrigger(UsageKeys.TRIGGER_BUZZ), 1) client.reconnect(onlyIfChanged = false, ignoreRetryDelays = true) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AllSettingsScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AllSettingsScreen.kt index e969cc41ea..171d3e2c51 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AllSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AllSettingsScreen.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.Arrangement import androidx.compose.foundation.layout.Box @@ -67,6 +67,7 @@ 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.SettingsSearchTopBar import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppPlatform import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsDivider import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsItem import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsSection @@ -79,6 +80,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.launch import org.jetbrains.compose.resources.StringResource @@ -113,12 +115,14 @@ fun AllSettingsScreen( // an input actually changes — not on every keystroke. `onResetMarmot` reads the volatile // `isResettingMarmot` through `rememberUpdatedState` so the memoized closure never goes stale. val onResetMarmot by rememberUpdatedState(newValue = { if (!isResettingMarmot) showResetMarmotDialog = true }) + val appPlatform = LocalAppPlatform.current val catalog = - remember(hasPrivateKey, nav, uriHandler) { + remember(hasPrivateKey, nav, uriHandler, appPlatform) { buildSettingsCatalog( nav = nav, uriHandler = uriHandler, hasPrivateKey = hasPrivateKey, + legal = appPlatform.legalSettingsCategory(uriHandler), onResetMarmot = { onResetMarmot() }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt index 2cbe1eb4f2..0bf5bee248 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/SettingsCatalogBuilder.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/SettingsCatalogBuilder.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.ui.platform.UriHandler import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol @@ -121,6 +121,7 @@ fun buildSettingsCatalog( nav: INav, uriHandler: UriHandler, hasPrivateKey: Boolean, + legal: SettingsCategory?, onResetMarmot: () -> Unit, ): List { // Most rows are a symbol icon + a keyword blob that navigates to a route. This local @@ -246,5 +247,5 @@ fun buildSettingsCatalog( }, ) - return listOfNotNull(account, app, legalSettingsCategory(uriHandler), danger) + return listOfNotNull(account, app, legal, danger) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt index fb35e33663..80bf3a1f50 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -57,7 +57,6 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow 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.navigation.Route @@ -76,6 +75,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_onchain_pending import com.vitorpamplona.amethyst.commons.resources.wallet_onchain_transactions import com.vitorpamplona.amethyst.commons.resources.wallet_outgoing import com.vitorpamplona.amethyst.commons.resources.wallet_refresh +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture @@ -83,11 +83,11 @@ import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.commons.ui.note.formatMonthDayTime import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.datasource.OnchainZapsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.datasource.OnchainZapsFilterAssemblerSubscription import kotlin.math.absoluteValue @OptIn(ExperimentalMaterial3Api::class) @@ -96,7 +96,7 @@ fun OnchainTransactionsScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val viewModel: OnchainTransactionsViewModel = viewModel() + val viewModel: OnchainTransactionsViewModel = rememberViewModel { OnchainTransactionsViewModel() } LaunchedEffect(accountViewModel) { viewModel.init(accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt index ff4709d301..3ed8baa40b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsViewModel.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel @@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.nipBCOnchainZaps.taproot.TaprootAddress import com.vitorpamplona.quartz.nipBCOnchainZaps.zap.OnchainZapEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletDetailScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletDetailScreen.kt index ad82a41d08..5412b9141a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletDetailScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletDetailScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -51,7 +51,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -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.navigation.Route @@ -62,6 +61,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_receive import com.vitorpamplona.amethyst.commons.resources.wallet_sats import com.vitorpamplona.amethyst.commons.resources.wallet_send import com.vitorpamplona.amethyst.commons.resources.wallet_transactions +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -75,7 +75,7 @@ fun WalletDetailScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val walletViewModel: WalletViewModel = viewModel() + val walletViewModel: WalletViewModel = rememberViewModel { WalletViewModel() } walletViewModel.init(accountViewModel) walletViewModel.selectWallet(walletId) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt index 6289767a30..34658fd786 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletReceiveScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletReceiveScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -59,7 +59,6 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow 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.resources.Res @@ -71,6 +70,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_creating_invoice import com.vitorpamplona.amethyst.commons.resources.wallet_description import com.vitorpamplona.amethyst.commons.resources.wallet_receive import com.vitorpamplona.amethyst.commons.resources.wallet_sats +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.screen.loggedIn.qrcode.QrCodeDrawer @@ -88,7 +88,7 @@ fun WalletReceiveScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val walletViewModel: WalletViewModel = viewModel() + val walletViewModel: WalletViewModel = rememberViewModel { WalletViewModel() } LaunchedEffect(accountViewModel, walletId) { walletViewModel.init(accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletSendScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletSendScreen.kt index 735b458de5..9687610c0d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletSendScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletSendScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -52,7 +52,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.text.font.FontWeight 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.resources.Res @@ -62,6 +61,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_pay import com.vitorpamplona.amethyst.commons.resources.wallet_payment_sending import com.vitorpamplona.amethyst.commons.resources.wallet_payment_success import com.vitorpamplona.amethyst.commons.resources.wallet_send +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.components.util.getText import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.SendState @@ -77,7 +77,7 @@ fun WalletSendScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val walletViewModel: WalletViewModel = viewModel() + val walletViewModel: WalletViewModel = rememberViewModel { WalletViewModel() } LaunchedEffect(accountViewModel, walletId) { walletViewModel.init(accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt index 95fb71e0c4..f2e3d61133 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletTransactionsScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -54,7 +54,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.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.navigation.Route @@ -73,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_refresh import com.vitorpamplona.amethyst.commons.resources.wallet_transactions import com.vitorpamplona.amethyst.commons.resources.wallet_transactions_load_failed import com.vitorpamplona.amethyst.commons.ui.components.EmptyState +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture @@ -94,7 +94,7 @@ fun WalletTransactionsScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val walletViewModel: WalletViewModel = viewModel() + val walletViewModel: WalletViewModel = rememberViewModel { WalletViewModel() } LaunchedEffect(accountViewModel, walletId) { walletViewModel.init(accountViewModel) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/datasource/OnchainZapsFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/datasource/OnchainZapsFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/datasource/OnchainZapsFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/datasource/OnchainZapsFilterAssemblerSubscription.kt index 247c2fdb01..e884350bed 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/datasource/OnchainZapsFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/datasource/OnchainZapsFilterAssemblerSubscription.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.wallet.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember From 4b6d9c7b6d98d11a618156a3cc7ba9abc5062e22 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 12:50:07 +0000 Subject: [PATCH 08/20] refactor: share the gallery picker (step 6 wave 6 seam) SelectedMedia and the gallery buttons (SelectFromGallery, SelectSingleFromGallery) move to commonsUI. A picked file's address is `expect abstract class MediaUri`: on Android it is `actual typealias MediaUri = android.net.Uri`, so every upload call site that reads `media.uri` as a Uri keeps compiling unchanged; desktop and iOS get a string-backed StringMediaUri. GallerySelect/GallerySelectSingle are expect/actual: Android keeps the photo picker launchers, desktop and iOS report a cancel until they have a picker. MediaMimeTypes (isAvif, extensionFromMimeType) moves to commons/service/uploads. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- ...StripperPoisonedFixtureInstrumentedTest.kt | 1 + .../AvifUploadPipelineInstrumentedTest.kt | 1 + .../service/uploads/MediaCompressor.kt | 1 + .../service/uploads/MetadataStripper.kt | 1 + .../service/uploads/MultiOrchestrator.kt | 2 +- .../uploads/PreviewMetadataCalculator.kt | 1 + .../uploads/blossom/BlossomUploader.kt | 2 +- .../service/uploads/nip96/Nip96Uploader.kt | 4 +- .../amethyst/ui/actions/EditPostView.kt | 2 +- .../amethyst/ui/actions/EditPostViewModel.kt | 2 +- .../amethyst/ui/actions/NewMediaModel.kt | 2 +- .../amethyst/ui/actions/NewMediaView.kt | 2 +- .../ui/actions/NewUserMetadataScreen.kt | 2 +- .../ui/actions/NewUserMetadataViewModel.kt | 2 +- .../ui/actions/uploads/SelectFromFiles.kt | 1 + .../uploads/SelectedMediaProcessing.kt | 1 + .../ui/actions/uploads/SharedMediaResolver.kt | 1 + .../ui/actions/uploads/ShowImageUploadItem.kt | 1 + .../ui/actions/uploads/TakePicture.kt | 1 + .../nip22Comments/CommentPostViewModel.kt | 2 +- .../nip22Comments/GenericCommentPostScreen.kt | 4 +- .../loggedIn/badges/post/NewBadgeDialog.kt | 2 +- .../loggedIn/badges/post/NewBadgeModel.kt | 2 +- .../metadata/BookmarkGroupMetadataScreen.kt | 2 +- .../BookmarkGroupMetadataViewModel.kt | 2 +- .../chats/cordnGroup/CordnComposer.kt | 2 +- .../chats/cordnGroup/CordnGroupChatScreen.kt | 2 +- .../chats/marmotGroup/CreateGroupScreen.kt | 2 +- .../chats/marmotGroup/EditGroupInfoScreen.kt | 2 +- .../chats/marmotGroup/MarmotGroupChatView.kt | 4 +- .../marmotGroup/MarmotGroupIconEditor.kt | 2 +- .../send/MarmotNewMessageViewModel.kt | 2 +- .../loggedIn/chats/minichat/MinichatScreen.kt | 4 +- .../privateDM/send/ChatNewMessageViewModel.kt | 2 +- .../chats/privateDM/send/NewGroupDMScreen.kt | 4 +- .../send/PrivateMessageEditFieldRow.kt | 4 +- .../concord/ConcordChannelScreen.kt | 4 +- .../send/ConcordNewMessageViewModel.kt | 2 +- .../metadata/ChannelMetadataScreen.kt | 2 +- .../metadata/ChannelMetadataViewModel.kt | 2 +- .../relayGroup/RelayGroupMetadataScreen.kt | 2 +- .../relayGroup/RelayGroupMetadataViewModel.kt | 2 +- .../send/ChannelNewMessageViewModel.kt | 2 +- .../chats/publicChannels/send/EditFieldRow.kt | 4 +- .../chats/utils/ChatFileUploadState.kt | 2 +- .../newCommunity/NewCommunityModel.kt | 2 +- .../newCommunity/NewCommunityScreen.kt | 2 +- .../nip23LongForm/LongFormPostScreen.kt | 6 +- .../nip23LongForm/LongFormPostViewModel.kt | 2 +- .../nip99Classifieds/NewProductScreen.kt | 4 +- .../nip99Classifieds/NewProductViewModel.kt | 2 +- .../emojipacks/display/AddEmojiDialog.kt | 2 +- .../emojipacks/display/EmojiPackViewModel.kt | 2 +- .../list/metadata/EmojiPackMetadataScreen.kt | 2 +- .../metadata/EmojiPackMetadataViewModel.kt | 2 +- .../loggedIn/geohash/GeoHashPostScreen.kt | 2 +- .../loggedIn/hashtag/HashtagPostScreen.kt | 2 +- .../loggedIn/home/ShortNotePostScreen.kt | 4 +- .../loggedIn/home/ShortNotePostViewModel.kt | 2 +- .../list/metadata/FollowPackMetadataScreen.kt | 2 +- .../metadata/FollowPackMetadataViewModel.kt | 2 +- .../list/metadata/PeopleListMetadataScreen.kt | 2 +- .../metadata/PeopleListMetadataViewModel.kt | 2 +- .../loggedIn/longs/NewLongVideoButton.kt | 4 +- .../loggedIn/music/NewMusicPlaylistScreen.kt | 2 +- .../music/NewMusicPlaylistViewModel.kt | 2 +- .../loggedIn/music/NewMusicTrackScreen.kt | 4 +- .../loggedIn/music/NewMusicTrackViewModel.kt | 2 +- .../loggedIn/nests/create/CreateNestSheet.kt | 2 +- .../nests/create/CreateNestViewModel.kt | 2 +- .../nests/room/chat/NestEditFieldRow.kt | 4 +- .../room/chat/NestNewMessageViewModel.kt | 2 +- .../publicMessages/NewPublicMessageScreen.kt | 4 +- .../NewPublicMessageViewModel.kt | 2 +- .../loggedIn/pictures/NewPictureButton.kt | 4 +- .../authoring/EditPodcastShowScreen.kt | 2 +- .../authoring/EditPodcastShowViewModel.kt | 2 +- .../authoring/NewPodcastEpisodeScreen.kt | 4 +- .../authoring/NewPodcastEpisodeViewModel.kt | 2 +- .../authoring/NewPodcastTrailerScreen.kt | 2 +- .../authoring/NewPodcastTrailerViewModel.kt | 2 +- .../loggedIn/profile/header/ProfileHeader.kt | 2 +- .../loggedIn/shorts/NewShortVideoButton.kt | 4 +- .../ui/screen/loggedIn/url/UrlPostScreen.kt | 2 +- .../loggedIn/video/NewVideoFeedButton.kt | 4 +- .../service/uploads/MediaMimeTypesTest.kt | 4 + .../blossom/BlossomUploaderExtensionTest.kt | 2 +- .../ui/actions/uploads/SelectedMediaTest.kt | 1 + .../service/uploads/MediaMimeTypes.kt | 2 +- .../actions/uploads/GallerySelect.android.kt | 97 +++++++++++++++++++ .../ui/actions/uploads/MediaUri.android.kt | 25 +++++ .../commons/ui/actions/uploads/MediaUri.kt | 31 ++++++ .../ui/actions/uploads/SelectFromGallery.kt | 76 ++------------- .../ui/actions/uploads/GallerySelect.ios.kt | 38 ++++++++ .../ui/actions/uploads/MediaUri.ios.kt | 32 ++++++ .../ui/actions/uploads/GallerySelect.jvm.kt | 38 ++++++++ .../ui/actions/uploads/MediaUri.jvm.kt | 32 ++++++ 97 files changed, 412 insertions(+), 166 deletions(-) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/service/uploads/MediaMimeTypes.kt (97%) create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.android.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/actions/uploads/SelectFromGallery.kt (68%) create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.ios.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.jvm.kt diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifMetadataStripperPoisonedFixtureInstrumentedTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifMetadataStripperPoisonedFixtureInstrumentedTest.kt index a2f3b19e03..f69bbe1f4c 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifMetadataStripperPoisonedFixtureInstrumentedTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifMetadataStripperPoisonedFixtureInstrumentedTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.appContext import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.contentUriFor import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.copyAssetToCache +import com.vitorpamplona.amethyst.commons.service.uploads.AVIF_MIME import org.junit.Assert.assertNotNull import org.junit.Assert.assertThrows import org.junit.Test diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt index 983d217776..1cbcf9677a 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt @@ -26,6 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.appContext import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.contentUriFor import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.copyAssetToCache +import com.vitorpamplona.amethyst.commons.service.uploads.AVIF_MIME import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt index c72093f6e8..c36b42ed1d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt @@ -26,6 +26,7 @@ import android.net.Uri import androidx.core.net.toUri import androidx.media3.common.MimeTypes import com.davotoula.lightcompressor.video.GifToMp4Converter +import com.vitorpamplona.amethyst.commons.service.uploads.isAvif import com.vitorpamplona.amethyst.ui.components.util.MediaCompressorFileUtils import com.vitorpamplona.quartz.utils.Log import id.zelory.compressor.Compressor diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt index 1d29223977..85c0bf54eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MetadataStripper.kt @@ -29,6 +29,7 @@ import android.media.MediaMuxer import android.net.Uri import androidx.core.net.toUri import androidx.exifinterface.media.ExifInterface +import com.vitorpamplona.amethyst.commons.service.uploads.isAvif import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CancellationException import java.io.File diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt index 797688b86c..38547b435f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt @@ -27,7 +27,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.utils.ciphers.NostrCipher diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/PreviewMetadataCalculator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/PreviewMetadataCalculator.kt index df2756435b..840a0c13da 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/PreviewMetadataCalculator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/PreviewMetadataCalculator.kt @@ -31,6 +31,7 @@ import androidx.annotation.RequiresApi import com.vitorpamplona.amethyst.commons.blurhash.toBlurhash import com.vitorpamplona.amethyst.commons.service.image.BlurhashWrapper import com.vitorpamplona.amethyst.commons.service.image.ThumbhashWrapper +import com.vitorpamplona.amethyst.commons.service.uploads.isAvif import com.vitorpamplona.amethyst.commons.thumbhash.toThumbhash import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag import com.vitorpamplona.quartz.utils.Log diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt index 7656472d23..f8aaa70f92 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt @@ -30,10 +30,10 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_delete_with_messag import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_to_server_with_message import com.vitorpamplona.amethyst.commons.service.HttpStatusMessages import com.vitorpamplona.amethyst.commons.service.upload.BlossomPaymentException +import com.vitorpamplona.amethyst.commons.service.uploads.extensionFromMimeType import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult import com.vitorpamplona.amethyst.service.uploads.PreviewMetadataCalculator -import com.vitorpamplona.amethyst.service.uploads.extensionFromMimeType import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.JsonMapper import com.vitorpamplona.quartz.nip01Core.core.toHexKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt index bd01fb4a5a..2f0b666713 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt @@ -31,9 +31,9 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.failed_to_delete_with_message import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_to_server_with_message import com.vitorpamplona.amethyst.commons.service.HttpStatusMessages +import com.vitorpamplona.amethyst.commons.service.uploads.AVIF_EXTENSION +import com.vitorpamplona.amethyst.commons.service.uploads.AVIF_MIME import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.AVIF_EXTENSION -import com.vitorpamplona.amethyst.service.uploads.AVIF_MIME import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult import com.vitorpamplona.amethyst.service.uploads.PreviewMetadataCalculator import com.vitorpamplona.quartz.nip01Core.core.JsonMapper diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index ef3dd4ae99..bf256fd3e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -80,6 +80,7 @@ import com.vitorpamplona.amethyst.commons.resources.lightning_invoice import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.BechLink import com.vitorpamplona.amethyst.commons.ui.components.OutlinedThinPaddingTextField import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -103,7 +104,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.playback.composable.VideoView import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt index 02deee05dc..bd80fe5d5d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt @@ -41,6 +41,7 @@ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord @@ -50,7 +51,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index a42bbb8c5c..530db071bf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -38,12 +38,12 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index abe7354538..2a6f13a96c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_description import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -83,7 +84,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt index 9e95251d74..0dc92bd0ad 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt @@ -75,13 +75,13 @@ import com.vitorpamplona.amethyst.commons.resources.twitter import com.vitorpamplona.amethyst.commons.resources.twitter_proof_url_template import com.vitorpamplona.amethyst.commons.resources.username import com.vitorpamplona.amethyst.commons.resources.website_url +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index 049774718c..54fade5061 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException @@ -41,7 +42,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip39ExtIdentities.GitHubIdentity import com.vitorpamplona.quartz.nip39ExtIdentities.MastodonIdentity import com.vitorpamplona.quartz.nip39ExtIdentities.TwitterIdentity diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt index e652175c14..58f1acc707 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.upload_file +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.commons.ui.stringRes import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaProcessing.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaProcessing.kt index 3651631454..8f5335300e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaProcessing.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaProcessing.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.actions.uploads +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator class SelectedMediaProcessing( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt index 2ff00e88c9..a7da527781 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.actions.uploads import android.content.Context import androidx.core.net.toUri +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt index d508587166..ddab643263 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt @@ -78,6 +78,7 @@ import com.vitorpamplona.amethyst.commons.resources.uploading_state_hashing import com.vitorpamplona.amethyst.commons.resources.uploading_state_ready import com.vitorpamplona.amethyst.commons.resources.uploading_state_server_processing import com.vitorpamplona.amethyst.commons.resources.uploading_state_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.AutoNonlazyGrid import com.vitorpamplona.amethyst.commons.ui.note.CloseIcon import com.vitorpamplona.amethyst.commons.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt index 8597e9dc22..151d93b0b3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt @@ -52,6 +52,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.no_camera_app_found import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.take_a_picture +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.stringRes import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt index fe18936d50..43ff22bf9c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt @@ -55,6 +55,7 @@ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.IAudience @@ -71,7 +72,6 @@ import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index 6c064c59c1..963b9a7e93 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -78,6 +78,8 @@ import com.vitorpamplona.amethyst.commons.resources.post_anonymously import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -109,8 +111,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPa import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.getActivity diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt index 7fc22bdedd..089ce1b0b4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt @@ -84,6 +84,7 @@ import com.vitorpamplona.amethyst.commons.resources.new_badge import com.vitorpamplona.amethyst.commons.resources.strip_metadata_description import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -94,7 +95,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import kotlinx.collections.immutable.persistentListOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt index b73ecb2534..b13cef87f8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt @@ -36,11 +36,11 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip58Badges.definition.tags.ThumbTag import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataScreen.kt index 939def90c5..719bbfe4eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataScreen.kt @@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.resources.follow_set_edit_list_metadat import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events import com.vitorpamplona.amethyst.commons.resources.optional_picture_url import com.vitorpamplona.amethyst.commons.resources.read_only_user +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar @@ -62,7 +63,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.SettingsCategoryFirstModifier import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt index 4cd31cfd2f..bca0a57abe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException @@ -47,7 +48,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt index 12bc6d3266..c72f18e79b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnComposer.kt @@ -49,6 +49,7 @@ import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestio import com.vitorpamplona.amethyst.commons.nip30CustomEmojis.ui.ShowEmojiSuggestionList import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.cordn_composer_hint +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -63,7 +64,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceMessagePreview import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt index 04b4e5f8f2..19114bc47b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt @@ -103,6 +103,7 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_voice_record import com.vitorpamplona.amethyst.commons.resources.cordn_voice_stop import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.loadStringRes @@ -119,7 +120,6 @@ import com.vitorpamplona.amethyst.model.cordn.CordnMediaService import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceMessageRecorder import com.vitorpamplona.amethyst.ui.components.ZoomableContentView import com.vitorpamplona.amethyst.ui.note.types.RenderAudioWaveformPlayer diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt index 016a0a22fb..964a47bc57 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt @@ -59,13 +59,13 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_keypackage_relays_not import com.vitorpamplona.amethyst.commons.resources.marmot_keypackage_relays_not_set_title import com.vitorpamplona.amethyst.commons.resources.marmot_skip_for_now import com.vitorpamplona.amethyst.commons.resources.marmot_use_outbox_relays +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.screen.loggedIn.uploadMarmotGroupIcon import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.utils.Log diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt index 9143123543..27df1823cd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt @@ -57,13 +57,13 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_group_name import com.vitorpamplona.amethyst.commons.resources.marmot_group_name_placeholder import com.vitorpamplona.amethyst.commons.resources.marmot_legacy_group_no_avatar_url import com.vitorpamplona.amethyst.commons.resources.save +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ActionTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.screen.loggedIn.uploadMarmotGroupIcon import com.vitorpamplona.quartz.nip01Core.core.HexKey import kotlinx.coroutines.Dispatchers diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt index 405682ed35..f2d5fbc593 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt @@ -67,6 +67,8 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_out_of_sync_confirm_b import com.vitorpamplona.amethyst.commons.resources.marmot_out_of_sync_confirm_title import com.vitorpamplona.amethyst.commons.resources.marmot_out_of_sync_reset import com.vitorpamplona.amethyst.commons.resources.reply_here +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -81,8 +83,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightCh import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.LocalChatIsOneOnOne import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt index 2a7cbd75ae..9aba7e30a3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt @@ -44,11 +44,11 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_add_photo import com.vitorpamplona.amethyst.commons.resources.marmot_change_photo import com.vitorpamplona.amethyst.commons.resources.marmot_group_icon import com.vitorpamplona.amethyst.commons.resources.marmot_remove_photo +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.core.HexKey /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt index 0d5e197d51..feac05bcb6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt @@ -33,11 +33,11 @@ import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.latestMarmotEdit import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState import com.vitorpamplona.quartz.nip01Core.core.HexKey import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt index a35ffee77e..21b6cfc90e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt @@ -67,6 +67,8 @@ import com.vitorpamplona.amethyst.commons.resources.chat_minichat_title import com.vitorpamplona.amethyst.commons.resources.chat_send_image_title import com.vitorpamplona.amethyst.commons.resources.concord_open_channel import com.vitorpamplona.amethyst.commons.resources.reply_here +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription @@ -76,8 +78,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.EditFieldModifier import com.vitorpamplona.amethyst.commons.ui.theme.EditFieldTrailingIconModifier import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.ChatroomMessageCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.LocalSuppressReplyToNoteId diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt index 5eee33b981..19ff582753 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt @@ -48,6 +48,7 @@ import com.vitorpamplona.amethyst.commons.model.location.LocationResult import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestionState import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.IZapField @@ -57,7 +58,6 @@ import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileSender import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.ChatFileUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index 15c46d0264..e54a13f17e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -87,6 +87,8 @@ import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -119,8 +121,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt index 1df6ad0988..3ca6a4270d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt @@ -62,6 +62,8 @@ import com.vitorpamplona.amethyst.commons.resources.retry_without_encryption import com.vitorpamplona.amethyst.commons.resources.this_message_will_disappear_in import com.vitorpamplona.amethyst.commons.resources.upload_without_encryption_warning import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -86,8 +88,6 @@ import com.vitorpamplona.amethyst.commons.util.showCount import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload.RoomChatFileUploadDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.DisplayReplyingToNote diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt index d79851774f..976230c024 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt @@ -73,6 +73,8 @@ import com.vitorpamplona.amethyst.commons.resources.concord_typing_many import com.vitorpamplona.amethyst.commons.resources.concord_typing_one import com.vitorpamplona.amethyst.commons.resources.concord_typing_two import com.vitorpamplona.amethyst.commons.resources.reply_here +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachCursor import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachMarkers @@ -93,8 +95,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightCh import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions.WatchAndLoadMyEmojiList import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.feed.RefreshingChatroomFeedView diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt index ea1c2768ab..6113ce792f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt @@ -34,12 +34,12 @@ import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestionState +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.ReplyMode -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState import com.vitorpamplona.quartz.concord.cord03Channels.ConcordChannelId import com.vitorpamplona.quartz.nip01Core.core.HexKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt index e61e2e42b9..760b1de02e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt @@ -61,6 +61,7 @@ import com.vitorpamplona.amethyst.commons.resources.public_chat_relays_explainer import com.vitorpamplona.amethyst.commons.resources.public_chat_relays_title import com.vitorpamplona.amethyst.commons.resources.public_chat_title import com.vitorpamplona.amethyst.commons.resources.read_only_user +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -78,7 +79,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt index 4fd0e53023..5f1c7228a8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @@ -50,7 +51,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip01Core.tags.events.ETag diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt index aff716d859..b19f9b0bfb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt @@ -104,6 +104,7 @@ import com.vitorpamplona.amethyst.commons.resources.relay_group_relay_no_nip29 import com.vitorpamplona.amethyst.commons.resources.relay_group_section_discovery import com.vitorpamplona.amethyst.commons.resources.relay_group_section_discovery_desc import com.vitorpamplona.amethyst.commons.resources.relay_group_section_permissions +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar @@ -114,7 +115,6 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.datasource.RelayGroupCardWarmupSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.ui.note.creators.location.LocationPreviewMap import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt index 17cb7d0c4c..0b9b54a6bf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt @@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException @@ -51,7 +52,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.buzz.workspace.BUZZ_CHANNEL_TYPE_FORUM import com.vitorpamplona.quartz.buzz.workspace.BUZZ_CHANNEL_TYPE_STREAM import com.vitorpamplona.quartz.buzz.workspace.newBuzzChannelId diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt index 4c61d9a10d..6c81894517 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt @@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.UrlParser import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord @@ -70,7 +71,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.ReplyMode import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt index b4f2124ed2..ca7c486751 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt @@ -53,6 +53,8 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_editing_banner import com.vitorpamplona.amethyst.commons.resources.cancel import com.vitorpamplona.amethyst.commons.resources.reply_here import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList @@ -66,8 +68,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightCh import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.DisplayReplyingToNote import com.vitorpamplona.quartz.utils.TimeUtils diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt index ea55a4ee76..cbb708ea7c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt @@ -28,8 +28,8 @@ import androidx.compose.runtime.setValue import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt index 8fe84605f6..85dc127fa6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt @@ -39,11 +39,11 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt index 978c94596b..c0d669ab26 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt @@ -93,6 +93,7 @@ import com.vitorpamplona.amethyst.commons.resources.new_community_rules_section import com.vitorpamplona.amethyst.commons.resources.remove import com.vitorpamplona.amethyst.commons.resources.save import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect import com.vitorpamplona.amethyst.commons.ui.components.Nip05OrPubkeyLine import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -107,7 +108,6 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relay import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPage import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.core.Address diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt index 129fc19262..c75226bce1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt @@ -101,6 +101,9 @@ import com.vitorpamplona.amethyst.commons.resources.url_slug_placeholder import com.vitorpamplona.amethyst.commons.resources.your_article_title import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -126,10 +129,7 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPa import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt index 470e776a6e..7856a09ef0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt @@ -57,6 +57,7 @@ import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState @@ -74,7 +75,6 @@ import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index 4536d7d26b..d8def28c87 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -52,6 +52,8 @@ import com.vitorpamplona.amethyst.commons.resources.lightning_invoice import com.vitorpamplona.amethyst.commons.resources.new_product import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -76,8 +78,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPage import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.navigation.navs.Nav diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt index c73f4fbe0a..0a19fa5d04 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt @@ -52,6 +52,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState @@ -65,7 +66,6 @@ import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt index 51c417b32e..4b1b0f326d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt @@ -56,10 +56,10 @@ import com.vitorpamplona.amethyst.commons.resources.emoji_public_explainer import com.vitorpamplona.amethyst.commons.resources.emoji_shortcode_invalid import com.vitorpamplona.amethyst.commons.resources.emoji_shortcode_label import com.vitorpamplona.amethyst.commons.resources.emoji_url_label +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt index 25c362c7bf..6f6a51f5e4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt @@ -38,6 +38,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException import com.vitorpamplona.amethyst.service.uploads.CompressorQuality @@ -45,7 +46,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag import kotlinx.coroutines.Dispatchers diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt index 6c6bdcfa5b..dcce6ee4e3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt @@ -71,6 +71,7 @@ import com.vitorpamplona.amethyst.commons.resources.emoji_pack_name_label import com.vitorpamplona.amethyst.commons.resources.emoji_pack_upload_image_cta import com.vitorpamplona.amethyst.commons.resources.emoji_pack_upload_image_hint import com.vitorpamplona.amethyst.commons.resources.new_emoji_pack +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar @@ -78,7 +79,6 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle @Composable fun EmojiPackMetadataScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt index 10a94b3a97..8d004ebec4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt @@ -42,6 +42,7 @@ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException @@ -50,7 +51,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt index 4422ea12d1..fb41fd271c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt @@ -27,8 +27,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.cache.LocalCache +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.navigation.navs.Nav import com.vitorpamplona.amethyst.ui.note.nip22Comments.CommentPostViewModel import com.vitorpamplona.amethyst.ui.note.nip22Comments.GenericCommentPostScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt index d43a6bc4ca..ebc46566e7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt @@ -27,8 +27,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.cache.LocalCache +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.navigation.navs.Nav import com.vitorpamplona.amethyst.ui.note.nip22Comments.CommentPostViewModel import com.vitorpamplona.amethyst.ui.note.nip22Comments.GenericCommentPostScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index f1469af6f3..8c3f3973e5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -109,6 +109,8 @@ import com.vitorpamplona.amethyst.commons.resources.schedule_post_always_on_prom import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -147,8 +149,6 @@ import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS import com.vitorpamplona.amethyst.ui.actions.uploads.RecordVoiceButton import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index 3ddf7c7f8a..09dc1dc05a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.service.ai.WritingTone import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.IAudience @@ -90,7 +91,6 @@ import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceAnonymizationController import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataScreen.kt index 82bf221d7f..79579f1835 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataScreen.kt @@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.commons.resources.follow_pack_title import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events import com.vitorpamplona.amethyst.commons.resources.picture_url import com.vitorpamplona.amethyst.commons.resources.read_only_user +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -66,7 +67,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt index 9429e26f05..5f38ec2d7b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException @@ -47,7 +48,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataScreen.kt index 34d71f6a98..4d2a4b3ecb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataScreen.kt @@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.commons.resources.people_list_explainer import com.vitorpamplona.amethyst.commons.resources.people_list_title import com.vitorpamplona.amethyst.commons.resources.picture_url import com.vitorpamplona.amethyst.commons.resources.read_only_user +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -66,7 +67,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonRow import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt index f015379575..721bd81a87 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException @@ -47,7 +48,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt index 23f91913bc..f5e4fadc27 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt @@ -50,6 +50,8 @@ import com.vitorpamplona.amethyst.commons.resources.ic_compose import com.vitorpamplona.amethyst.commons.resources.new_long_video import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -58,8 +60,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.NewMediaModel import com.vitorpamplona.amethyst.ui.actions.NewMediaView -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideo import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt index b5b837f382..a02d8290e2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt @@ -94,6 +94,7 @@ import com.vitorpamplona.amethyst.commons.resources.music_playlist_unknown_track import com.vitorpamplona.amethyst.commons.resources.music_playlist_uploading_banner import com.vitorpamplona.amethyst.commons.resources.new_music_playlist import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav @@ -105,7 +106,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent import kotlinx.collections.immutable.persistentListOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt index 179566e82a..664693d762 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.CompressorQuality @@ -34,7 +35,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadingState -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.experimental.music.playlist.MusicPlaylistEvent import com.vitorpamplona.quartz.nip01Core.core.Address import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt index 4c130e5156..900b47f545 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt @@ -84,6 +84,8 @@ import com.vitorpamplona.amethyst.commons.resources.music_track_title_placeholde import com.vitorpamplona.amethyst.commons.resources.music_track_uploading_banner import com.vitorpamplona.amethyst.commons.resources.new_music_track import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -92,8 +94,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import kotlinx.collections.immutable.persistentListOf @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt index 602f125695..281cbf3855 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt @@ -30,13 +30,13 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent import com.vitorpamplona.quartz.nip01Core.core.Address import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestSheet.kt index c062d737cc..2ea4775057 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestSheet.kt @@ -70,10 +70,10 @@ import com.vitorpamplona.amethyst.commons.resources.nest_create_submit import com.vitorpamplona.amethyst.commons.resources.nest_create_title import com.vitorpamplona.amethyst.commons.resources.nest_create_when import com.vitorpamplona.amethyst.commons.resources.next +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.NestsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt index 314f882a8a..09bd48895a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt @@ -31,6 +31,7 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException @@ -39,7 +40,6 @@ import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.endpoint diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt index 11b20b6327..2f33c77186 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt @@ -40,6 +40,8 @@ import com.vitorpamplona.amethyst.commons.nip30CustomEmojis.ui.ShowEmojiSuggesti import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.reply_here import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -51,8 +53,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightCh import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.DisplayReplyingToNote import kotlinx.collections.immutable.persistentListOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt index cb1dd4ce5c..6201c18504 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt @@ -50,6 +50,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.UrlParser import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord @@ -60,7 +61,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index c8fbb8787c..cc158ece0c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -60,6 +60,8 @@ import com.vitorpamplona.amethyst.commons.resources.messages_new_message_to_capt import com.vitorpamplona.amethyst.commons.resources.public_message import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -90,8 +92,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt index b28456d3db..bfdf88f3bf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt @@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState @@ -67,7 +68,6 @@ import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt index 29e9486023..173f7abcdd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt @@ -51,6 +51,8 @@ import com.vitorpamplona.amethyst.commons.resources.ic_compose import com.vitorpamplona.amethyst.commons.resources.new_picture import com.vitorpamplona.amethyst.commons.resources.take_a_picture import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -59,8 +61,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.NewMediaModel import com.vitorpamplona.amethyst.ui.actions.NewMediaView -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePicture import com.vitorpamplona.amethyst.ui.actions.uploads.resolveSharedMedia import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt index 4007521128..0c21bcadec 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt @@ -74,12 +74,12 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_show_type_label import com.vitorpamplona.amethyst.commons.resources.podcast_show_type_serial import com.vitorpamplona.amethyst.commons.resources.podcast_show_website_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.CoverImagePicker import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadInProgressBanner import kotlinx.collections.immutable.persistentListOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt index d5f77f1e98..8a0c2c99ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt @@ -28,10 +28,10 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.podcasts.V4VSplitEditorState import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent import com.vitorpamplona.quartz.nipXXPodcasting20.metadata.Podcasting20PodcastMetadata diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt index c21e4a2857..45a242d68c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt @@ -90,13 +90,13 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_episode_video_label import com.vitorpamplona.amethyst.commons.resources.podcast_new_episode import com.vitorpamplona.amethyst.commons.resources.podcast_publishing_banner import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.CoverImagePicker import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadInProgressBanner import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadPlaceholder diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt index a43c37fa88..72f55d4a5f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt @@ -29,10 +29,10 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.podcasts.V4VSplitEditorState import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent import com.vitorpamplona.quartz.podcasts.PodcastAudio diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt index 81f617a207..dc9bba01bd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt @@ -71,12 +71,12 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_upload_cta import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_upload_hint import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_url_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadInProgressBanner import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadPlaceholder diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt index 19ad47e7cf..d1e649b0ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt @@ -26,10 +26,10 @@ import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt index 49f43cad99..e88fa61fef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt @@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserPicture import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.upload_image import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture @@ -70,7 +71,6 @@ 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.actions.NewUserMetadataViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel import kotlinx.coroutines.launch diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt index 4abf06c4dd..5bc79a1bec 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt @@ -52,6 +52,8 @@ import com.vitorpamplona.amethyst.commons.resources.ic_compose import com.vitorpamplona.amethyst.commons.resources.new_short_video import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -60,8 +62,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.NewMediaModel import com.vitorpamplona.amethyst.ui.actions.NewMediaView -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideo import com.vitorpamplona.amethyst.ui.actions.uploads.resolveSharedMedia import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt index 2844a3f4a2..1709d2341d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt @@ -27,8 +27,8 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.cache.LocalCache +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.navigation.navs.Nav import com.vitorpamplona.amethyst.ui.note.nip22Comments.CommentPostViewModel import com.vitorpamplona.amethyst.ui.note.nip22Comments.GenericCommentPostScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt index 905532836c..77f429b696 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt @@ -52,6 +52,8 @@ import com.vitorpamplona.amethyst.commons.resources.new_media import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.take_a_picture import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -60,8 +62,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.NewMediaModel import com.vitorpamplona.amethyst.ui.actions.NewMediaView -import com.vitorpamplona.amethyst.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.ui.actions.uploads.TakePicture import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideo import com.vitorpamplona.amethyst.ui.actions.uploads.resolveSharedMedia diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/MediaMimeTypesTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/MediaMimeTypesTest.kt index bbe0a2c08e..7af7a5dd44 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/MediaMimeTypesTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/MediaMimeTypesTest.kt @@ -20,6 +20,10 @@ */ package com.vitorpamplona.amethyst.service.uploads +import com.vitorpamplona.amethyst.commons.service.uploads.AVIF_EXTENSION +import com.vitorpamplona.amethyst.commons.service.uploads.AVIF_MIME +import com.vitorpamplona.amethyst.commons.service.uploads.extensionFromMimeType +import com.vitorpamplona.amethyst.commons.service.uploads.isAvif import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNull diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploaderExtensionTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploaderExtensionTest.kt index 951779323d..f65547d8f3 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploaderExtensionTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploaderExtensionTest.kt @@ -20,7 +20,7 @@ */ package com.vitorpamplona.amethyst.service.uploads.blossom -import com.vitorpamplona.amethyst.service.uploads.extensionFromMimeType +import com.vitorpamplona.amethyst.commons.service.uploads.extensionFromMimeType import org.junit.Assert.assertEquals import org.junit.Test diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt index 7d8d421590..ed8b30593e 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.ui.actions.uploads import android.net.Uri +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import io.mockk.mockk import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaMimeTypes.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaMimeTypes.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaMimeTypes.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaMimeTypes.kt index 7de127608b..c6e553602d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaMimeTypes.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaMimeTypes.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.uploads +package com.vitorpamplona.amethyst.commons.service.uploads // RFC 9081 defines image/avif for both still and animated AVIF. // image/avif-sequence is NOT IANA-registered and is intentionally not handled. diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt new file mode 100644 index 0000000000..44aa42fa16 --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt @@ -0,0 +1,97 @@ +/* + * 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.ui.actions.uploads + +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import java.util.concurrent.atomic.AtomicBoolean + +@Composable +actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { + val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } + val resolver = LocalContext.current.contentResolver + + val launcher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickMultipleVisualMedia(10), + onResult = { uris: List -> + onImageUri( + uris + .map { + SelectedMedia(it, resolver.getType(it)) + }.toImmutableList(), + ) + hasLaunched.set(false) + }, + ) + + @Composable + fun LaunchGallery() { + SideEffect { + if (!hasLaunched.getAndSet(true)) { + launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) + } + } + } + + LaunchGallery() +} + +@Composable +actual fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit) { + val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } + val resolver = LocalContext.current.contentResolver + + val launcher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { uri: Uri? -> + if (uri != null) { + onImageUri(SelectedMedia(uri, resolver.getType(uri))) + } else { + onImageUri(null) + } + + hasLaunched.set(false) + }, + ) + + @Composable + fun LaunchGallery() { + SideEffect { + if (!hasLaunched.getAndSet(true)) { + launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) + } + } + } + + LaunchGallery() +} diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.android.kt new file mode 100644 index 0000000000..41ef7a7878 --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.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.ui.actions.uploads + +import android.net.Uri + +actual typealias MediaUri = Uri diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.kt new file mode 100644 index 0000000000..fd3fca4400 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.kt @@ -0,0 +1,31 @@ +/* + * 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.ui.actions.uploads + +/** + * A picked file's address as the platform hands it over: Android's `content://` [android.net.Uri] + * (a typealias there, so app code keeps reading it as a `Uri`), a string-backed subclass + * elsewhere. + */ +expect abstract class MediaUri { + /** The address as a string (`content://…` on Android). */ + abstract override fun toString(): String +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromGallery.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt similarity index 68% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromGallery.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt index 773711d248..922bbc3537 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromGallery.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt @@ -18,16 +18,11 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.uploads +package com.vitorpamplona.amethyst.commons.ui.actions.uploads -import android.net.Uri -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.height import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -35,22 +30,19 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.service.uploads.isAvif import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.commons.ui.stringRes -import com.vitorpamplona.amethyst.service.uploads.isAvif import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import java.util.concurrent.atomic.AtomicBoolean @Stable class SelectedMedia( - val uri: Uri, + val uri: MediaUri, val mimeType: String?, ) { fun isImage() = mimeType?.startsWith("image") @@ -161,64 +153,10 @@ private fun GallerySelectButton( } } +/** Opens the platform's photo/video picker (up to 10 items); reports an empty list on cancel. */ @Composable -fun GallerySelect(onImageUri: (ImmutableList) -> Unit = {}) { - val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } - val resolver = LocalContext.current.contentResolver - - val launcher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.PickMultipleVisualMedia(10), - onResult = { uris: List -> - onImageUri( - uris - .map { - SelectedMedia(it, resolver.getType(it)) - }.toImmutableList(), - ) - hasLaunched.set(false) - }, - ) - - @Composable - fun LaunchGallery() { - SideEffect { - if (!hasLaunched.getAndSet(true)) { - launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) - } - } - } - - LaunchGallery() -} +expect fun GallerySelect(onImageUri: (ImmutableList) -> Unit = {}) +/** Opens the platform's photo/video picker for one item; reports null on cancel. */ @Composable -fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit = {}) { - val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } - val resolver = LocalContext.current.contentResolver - - val launcher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.PickVisualMedia(), - onResult = { uri: Uri? -> - if (uri != null) { - onImageUri(SelectedMedia(uri, resolver.getType(uri))) - } else { - onImageUri(null) - } - - hasLaunched.set(false) - }, - ) - - @Composable - fun LaunchGallery() { - SideEffect { - if (!hasLaunched.getAndSet(true)) { - launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) - } - } - } - - LaunchGallery() -} +expect fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit = {}) diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt new file mode 100644 index 0000000000..0b211f11fa --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt @@ -0,0 +1,38 @@ +/* + * 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.ui.actions.uploads + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +// No picker wired on this platform yet: report a cancel so callers close their picker state. + +@Composable +actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { + LaunchedEffect(Unit) { onImageUri(persistentListOf()) } +} + +@Composable +actual fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit) { + LaunchedEffect(Unit) { onImageUri(null) } +} diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.ios.kt new file mode 100644 index 0000000000..f9a5d2cf33 --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.ios.kt @@ -0,0 +1,32 @@ +/* + * 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.ui.actions.uploads + +actual abstract class MediaUri { + actual abstract override fun toString(): String +} + +/** A picked file addressed by a path or URL string. */ +class StringMediaUri( + val value: String, +) : MediaUri() { + override fun toString(): String = value +} diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt new file mode 100644 index 0000000000..0b211f11fa --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt @@ -0,0 +1,38 @@ +/* + * 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.ui.actions.uploads + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +// No picker wired on this platform yet: report a cancel so callers close their picker state. + +@Composable +actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { + LaunchedEffect(Unit) { onImageUri(persistentListOf()) } +} + +@Composable +actual fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit) { + LaunchedEffect(Unit) { onImageUri(null) } +} diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.jvm.kt new file mode 100644 index 0000000000..f9a5d2cf33 --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.jvm.kt @@ -0,0 +1,32 @@ +/* + * 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.ui.actions.uploads + +actual abstract class MediaUri { + actual abstract override fun toString(): String +} + +/** A picked file addressed by a path or URL string. */ +class StringMediaUri( + val value: String, +) : MediaUri() { + override fun toString(): String = value +} From b752a42d0180164578fa96035110bdea80d10ebf Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 12:56:36 +0000 Subject: [PATCH 09/20] refactor: JavaSerializable, shared notices in the NIP-46 signer, client-injected subscription stats - JavaSerializable (commons): java.io.Serializable on JVM/Android, a marker on iOS, for the chat list's saveable lazy keys. - Nip46SignerScreen drops its Toasts (and an unused Context parameter) for the shared notices. - ActiveSubscriptionsViewModel polls the client the screen hands it instead of Amethyst.instance. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../chats/rooms/feed/ChatroomListFeedView.kt | 4 +-- .../ActiveSubscriptionsScreen.kt | 2 +- .../ActiveSubscriptionsViewModel.kt | 12 ++++----- .../settings/nip46/Nip46SignerScreen.kt | 19 ++++++------- .../amethyst/commons/util/JavaSerializable.kt | 27 +++++++++++++++++++ .../commons/util/JavaSerializable.ios.kt | 23 ++++++++++++++++ .../util/JavaSerializable.jvmAndroid.kt | 23 ++++++++++++++++ 7 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.kt create mode 100644 commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.ios.kt create mode 100644 commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.jvmAndroid.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt index 4e269a3929..8a536c9a44 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/rooms/feed/ChatroomListFeedView.kt @@ -64,6 +64,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding +import com.vitorpamplona.amethyst.commons.util.JavaSerializable import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChatroomHeaderCompose import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent @@ -81,7 +82,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import java.io.Serializable @Composable fun ChatroomListFeedView( @@ -302,7 +302,7 @@ private fun relayShortName(relay: NormalizedRelayUrl): String = // stores LazyColumn item keys in a SaveableStateHolder, which on Android // requires Bundle-storable types, so each variant is Serializable and only // holds primitives. -private sealed interface ChatroomLazyKey : Serializable +private sealed interface ChatroomLazyKey : JavaSerializable private data class MarmotChatroomLazyKey( val groupId: HexKey, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt index 1b7c903bd3..6c79da8714 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt @@ -127,7 +127,7 @@ fun ActiveSubscriptionsScreen( nav: INav, viewModel: ActiveSubscriptionsViewModel = viewModel(), ) { - LaunchedEffect(Unit) { viewModel.startPolling() } + LaunchedEffect(Unit) { viewModel.startPolling(accountViewModel.account.client) } val state by viewModel.state.collectAsStateWithLifecycle() Scaffold( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt index a5db379f0b..b12cbe3667 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt @@ -23,14 +23,15 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.subscriptions import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.topNavFeeds.IFeedTopNavPerRelayFilter import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -143,18 +144,17 @@ class ActiveSubscriptionsViewModel : ViewModel() { val state: StateFlow = _state.asStateFlow() /** Polls while the screen is on. [REFRESH_MS] is slow enough to be free, fast enough to feel live. */ - fun startPolling() { + fun startPolling(client: INostrClient) { viewModelScope.launch(Dispatchers.Default) { while (isActive) { - _state.value = snapshot() - kotlinx.coroutines.delay(REFRESH_MS) + _state.value = snapshot(client) + delay(REFRESH_MS) } } } - private suspend fun snapshot(): ActiveSubscriptionsState = + private suspend fun snapshot(client: INostrClient): ActiveSubscriptionsState = withContext(Dispatchers.Default) { - val client = Amethyst.instance.client aggregateSubscriptions( client.connectedRelaysFlow().value.associateWith { relay -> client.activeRequests(relay).values.flatten() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt index d3807f25ce..22e1ef4386 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt @@ -20,8 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.nip46 -import android.content.Context -import android.widget.Toast import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.RepeatMode import androidx.compose.animation.core.animateFloat @@ -73,7 +71,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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.TextAlign import androidx.compose.ui.unit.dp @@ -119,6 +116,8 @@ import com.vitorpamplona.amethyst.commons.resources.nip46_signer_scan_connect import com.vitorpamplona.amethyst.commons.resources.nip46_signer_status_no_relays import com.vitorpamplona.amethyst.commons.resources.nip46_signer_title import com.vitorpamplona.amethyst.commons.resources.nip46_signer_turn_on +import com.vitorpamplona.amethyst.commons.ui.components.rememberLongNotice +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.navigation.navs.INav @@ -142,7 +141,8 @@ fun Nip46SignerScreen( val account = accountViewModel.account val signer = account.nip46Signer val scope = rememberCoroutineScope() - val context = LocalContext.current + val shortNotice = rememberShortNotice() + val longNotice = rememberLongNotice() val clipboard = LocalClipboard.current val copiedStr = stringRes(Res.string.nip46_signer_copied) val regeneratedStr = stringRes(Res.string.nip46_signer_regenerated) @@ -175,7 +175,7 @@ fun Nip46SignerScreen( fun onConnect(uri: String) { scope.launch { val result = signer.connectViaNostrConnect(uri.trim()) - Toast.makeText(context, describe(context, result), Toast.LENGTH_LONG).show() + longNotice.show(describe(result)) refreshKey++ } } @@ -231,7 +231,7 @@ fun Nip46SignerScreen( uri = uri, onCopy = { scope.launch { clipboard.setText(uri) } - Toast.makeText(context, copiedStr, Toast.LENGTH_SHORT).show() + shortNotice.show(copiedStr) }, onRegenerate = { confirmRotate = true }, ) @@ -273,7 +273,7 @@ fun Nip46SignerScreen( onConfirm = { confirmRotate = false signer.rotateAddress() - Toast.makeText(context, regeneratedStr, Toast.LENGTH_SHORT).show() + shortNotice.show(regeneratedStr) }, onDismiss = { confirmRotate = false }, ) @@ -625,10 +625,7 @@ private fun ReadOnlyNotice() { } } -private suspend fun describe( - context: Context, - result: Nip46SignerState.ConnectResult, -): String = +private suspend fun describe(result: Nip46SignerState.ConnectResult): String = when (result) { is Nip46SignerState.ConnectResult.Connected -> result.name?.let { loadStringRes(Res.string.nip46_signer_connected_named, it) } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.kt new file mode 100644 index 0000000000..c0fd6badf2 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.kt @@ -0,0 +1,27 @@ +/* + * 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 + +/** + * `java.io.Serializable` where there is a JVM, a marker elsewhere. For values Android must store in + * a Bundle (saveable state, lazy-list keys) that shared code declares. + */ +expect interface JavaSerializable diff --git a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.ios.kt new file mode 100644 index 0000000000..32f98452cd --- /dev/null +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.ios.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 interface JavaSerializable diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.jvmAndroid.kt b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.jvmAndroid.kt new file mode 100644 index 0000000000..580775f5e5 --- /dev/null +++ b/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/util/JavaSerializable.jvmAndroid.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 typealias JavaSerializable = java.io.Serializable From 8333d8b78f0786db9caecf6dd4e33dc72dba982c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 13:16:11 +0000 Subject: [PATCH 10/20] refactor: share URL highlighting, image paste and dialog edge-to-edge - UrlUserTagOutputTransformation highlights links with quartz's UrlDetector (what the note renderer links with) instead of android.util.Patterns.WEB_URL. - ThinPaddingTextField's image paste goes through Modifier.imageContentReceiver (commonsUI, Android actual; a no-op elsewhere) and reports a MediaUri. - SetDialogToEdgeToEdge becomes expect/actual in commonsUI (Android fixes the dialog window; desktop and iOS need nothing). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/ui/actions/NewMediaView.kt | 2 +- .../actions/UrlUserTagOutputTransformation.kt | 20 ++++--- .../ui/components/ThinPaddingTextField.kt | 36 ++---------- .../loggedIn/badges/post/NewBadgeDialog.kt | 2 +- .../chats/utils/ChatFileUploadDialog.kt | 2 +- .../loggedIn/home/AddOutboxRelayListDialog.kt | 2 +- .../notifications/AddInboxRelayListDialog.kt | 2 +- .../qrcode/scanner/QrScannerDialog.kt | 2 +- .../relays/dm/AddDMRelayListDialog.kt | 2 +- .../search/AddSearchRelayListDialog.kt | 2 +- .../amethyst/ui/tor/TorSettingsDialog.kt | 2 +- .../ImageContentReceiver.android.kt | 58 +++++++++++++++++++ .../SetDialogToEdgeToEdge.android.kt | 5 +- .../ui/components/ImageContentReceiver.kt | 30 ++++++++++ .../ui/components/SetDialogToEdgeToEdge.kt | 30 ++++++++++ .../ui/components/ImageContentReceiver.ios.kt | 26 +++++++++ .../components/SetDialogToEdgeToEdge.ios.kt | 26 +++++++++ .../ui/components/ImageContentReceiver.jvm.kt | 26 +++++++++ .../components/SetDialogToEdgeToEdge.jvm.kt | 26 +++++++++ 19 files changed, 248 insertions(+), 53 deletions(-) create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SetDialogToEdgeToEdge.kt => commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.kt (94%) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 2a6f13a96c..3142596c5c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -73,6 +73,7 @@ import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_description import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -85,7 +86,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt index 8306f7eba2..ba59fe6ca9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/UrlUserTagOutputTransformation.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.text.style.TextDecoration import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.ui.text.MENTION_REGEX import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull +import com.vitorpamplona.quartz.utils.urldetector.detection.UrlDetector import kotlin.coroutines.cancellation.CancellationException class UrlUserTagOutputTransformation( @@ -76,16 +77,17 @@ class UrlUserTagOutputTransformation( } } +// The same detector the note renderer links with, so the editor highlights what the posted +// note will make clickable. private fun TextFieldBuffer.highlightUrls(color: Color) { val text = asCharSequence().toString() - val urlPattern = android.util.Patterns.WEB_URL - - val matcher = urlPattern.matcher(text) - while (matcher.find()) { - addStyle( - SpanStyle(color = color, textDecoration = TextDecoration.None), - matcher.start(), - matcher.end(), - ) + var searchFrom = 0 + UrlDetector(text).detect().forEach { url -> + val start = text.indexOf(url.originalUrl, searchFrom) + if (start >= 0) { + val end = start + url.originalUrl.length + addStyle(SpanStyle(color = color, textDecoration = TextDecoration.None), start, end) + searchFrom = end + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt index e1caaddd21..4316aa294a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt @@ -20,14 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.components -import android.net.Uri import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.content.MediaType -import androidx.compose.foundation.content.ReceiveContentListener -import androidx.compose.foundation.content.TransferableContent -import androidx.compose.foundation.content.consume -import androidx.compose.foundation.content.contentReceiver -import androidx.compose.foundation.content.hasMediaType import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.PaddingValues @@ -60,6 +53,8 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri +import com.vitorpamplona.amethyst.commons.ui.components.imageContentReceiver import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText // COPIED FROM TEXT FIELD @@ -72,7 +67,7 @@ fun ThinPaddingTextField( state: TextFieldState, modifier: Modifier = Modifier, onTextChanged: (() -> Unit)? = null, - onContentReceived: ((Uri, String?) -> Unit)? = null, + onContentReceived: ((MediaUri, String?) -> Unit)? = null, enabled: Boolean = true, readOnly: Boolean = false, textStyle: TextStyle = LocalTextStyle.current, @@ -142,30 +137,7 @@ fun ThinPaddingTextField( val contentModifier = if (onContentReceived != null) { - modifier.contentReceiver( - object : ReceiveContentListener { - override fun onReceive(transferableContent: TransferableContent): TransferableContent? { - if (!transferableContent.hasMediaType(MediaType.Image)) { - return transferableContent - } - val remaining = - transferableContent.consume { item -> - val uri = item.uri - if (uri != null) { - onContentReceived( - uri, - transferableContent.clipEntry.clipData.description - .getMimeType(0), - ) - true - } else { - false - } - } - return remaining - } - }, - ) + modifier.imageContentReceiver(onContentReceived) } else { modifier } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt index 089ce1b0b4..67f73fb063 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt @@ -85,6 +85,7 @@ import com.vitorpamplona.amethyst.commons.resources.strip_metadata_description import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -96,7 +97,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt index 12e4df74a7..c9e48c904f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt @@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.media_compression_quality_me import com.vitorpamplona.amethyst.commons.resources.media_compression_quality_uncompressed import com.vitorpamplona.amethyst.commons.resources.strip_metadata_description import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -86,7 +87,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AddOutboxRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AddOutboxRelayListDialog.kt index 374ab4452a..34932d3318 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AddOutboxRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AddOutboxRelayListDialog.kt @@ -40,13 +40,13 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.outbox_relays_not_found_editing import com.vitorpamplona.amethyst.commons.resources.outbox_relays_not_found_examples import com.vitorpamplona.amethyst.commons.resources.outbox_relays_title +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.Nip65OutboxRelayList import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.Nip65RelayListViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/AddInboxRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/AddInboxRelayListDialog.kt index 95b78161eb..5b6ac5f244 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/AddInboxRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/AddInboxRelayListDialog.kt @@ -40,13 +40,13 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.inbox_relays_not_found_editing import com.vitorpamplona.amethyst.commons.resources.inbox_relays_not_found_examples import com.vitorpamplona.amethyst.commons.resources.inbox_relays_title +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.Nip65InboxRelayList import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.nip65.Nip65RelayListViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt index d1dbf238eb..ddb6f718ad 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/qrcode/scanner/QrScannerDialog.kt @@ -104,10 +104,10 @@ import com.vitorpamplona.amethyst.commons.resources.qr_scanner_grant_camera import com.vitorpamplona.amethyst.commons.resources.qr_scanner_no_code_in_image import com.vitorpamplona.amethyst.commons.resources.qr_scanner_open_settings import com.vitorpamplona.amethyst.commons.resources.qr_scanner_unavailable +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ScanOutcome import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.ui.call.openAppSettings -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt index 75a2b34815..fa587725a0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/AddDMRelayListDialog.kt @@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.commons.resources.default_relays_longer import com.vitorpamplona.amethyst.commons.resources.dm_relays_not_found_editing import com.vitorpamplona.amethyst.commons.resources.dm_relays_not_found_examples2 import com.vitorpamplona.amethyst.commons.resources.dm_relays_title +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @@ -50,7 +51,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge @OptIn(ExperimentalMaterial3Api::class) @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt index 030057dd11..bb8fe5a062 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/AddSearchRelayListDialog.kt @@ -43,6 +43,7 @@ import com.vitorpamplona.amethyst.commons.resources.default_relays_longer import com.vitorpamplona.amethyst.commons.resources.search_relays_not_found_editing import com.vitorpamplona.amethyst.commons.resources.search_relays_not_found_examples import com.vitorpamplona.amethyst.commons.resources.search_relays_title +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder @@ -50,7 +51,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayList import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.search.SearchRelayListViewModel import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt index 1d73afba3c..7621bdf3c1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/tor/TorSettingsDialog.kt @@ -85,6 +85,7 @@ import com.vitorpamplona.amethyst.commons.tor.explainerId import com.vitorpamplona.amethyst.commons.tor.parseTorPresetType import com.vitorpamplona.amethyst.commons.tor.parseTorType import com.vitorpamplona.amethyst.commons.tor.resourceId +import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow @@ -92,7 +93,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText -import com.vitorpamplona.amethyst.ui.components.SetDialogToEdgeToEdge import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CancellationException import org.jetbrains.compose.resources.StringResource diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt new file mode 100644 index 0000000000..b7aa2f604a --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt @@ -0,0 +1,58 @@ +/* + * 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.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.content.MediaType +import androidx.compose.foundation.content.ReceiveContentListener +import androidx.compose.foundation.content.TransferableContent +import androidx.compose.foundation.content.consume +import androidx.compose.foundation.content.contentReceiver +import androidx.compose.foundation.content.hasMediaType +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri + +@OptIn(ExperimentalFoundationApi::class) +actual fun Modifier.imageContentReceiver(onImage: (MediaUri, String?) -> Unit): Modifier = + contentReceiver( + object : ReceiveContentListener { + override fun onReceive(transferableContent: TransferableContent): TransferableContent? { + if (!transferableContent.hasMediaType(MediaType.Image)) { + return transferableContent + } + val remaining = + transferableContent.consume { item -> + val uri = item.uri + if (uri != null) { + onImage( + uri, + transferableContent.clipEntry.clipData.description + .getMimeType(0), + ) + true + } else { + false + } + } + return remaining + } + }, + ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SetDialogToEdgeToEdge.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SetDialogToEdgeToEdge.kt rename to commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.kt index fadf594304..b80738bd5d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SetDialogToEdgeToEdge.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.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.components +package com.vitorpamplona.amethyst.commons.ui.components import android.view.View import android.view.WindowManager @@ -29,11 +29,10 @@ import androidx.compose.runtime.SideEffect import androidx.compose.ui.platform.LocalView import androidx.compose.ui.window.DialogWindowProvider import androidx.core.view.WindowCompat -import com.vitorpamplona.amethyst.commons.ui.components.getActivityWindow import com.vitorpamplona.amethyst.commons.ui.theme.isLight @Composable -fun SetDialogToEdgeToEdge() { +actual fun SetDialogToEdgeToEdge() { val activityWindow = getActivityWindow() val dialogWindow = (LocalView.current.parent as? DialogWindowProvider)?.window val parentView = LocalView.current.parent as View diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.kt new file mode 100644 index 0000000000..d603ebb1b1 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.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.amethyst.commons.ui.components + +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri + +/** + * Accepts images pasted or dragged into the field (keyboard GIFs, stickers, clipboard images), + * reporting each one's address and MIME type. Android only for now; elsewhere it adds nothing. + */ +expect fun Modifier.imageContentReceiver(onImage: (MediaUri, String?) -> Unit): Modifier diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.kt new file mode 100644 index 0000000000..ac8b90e9e4 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.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.amethyst.commons.ui.components + +import androidx.compose.runtime.Composable + +/** + * Stretches the enclosing dialog's window over the whole activity and matches its system-bar + * icon colours to the theme. Android only; other platforms' dialogs need no such fix. + */ +@Composable +expect fun SetDialogToEdgeToEdge() diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt new file mode 100644 index 0000000000..6c08448555 --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt @@ -0,0 +1,26 @@ +/* + * 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.ui.components + +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri + +actual fun Modifier.imageContentReceiver(onImage: (MediaUri, String?) -> Unit): Modifier = this diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt new file mode 100644 index 0000000000..5cdb61e42b --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt @@ -0,0 +1,26 @@ +/* + * 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.ui.components + +import androidx.compose.runtime.Composable + +@Composable +actual fun SetDialogToEdgeToEdge() {} diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt new file mode 100644 index 0000000000..6c08448555 --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt @@ -0,0 +1,26 @@ +/* + * 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.ui.components + +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri + +actual fun Modifier.imageContentReceiver(onImage: (MediaUri, String?) -> Unit): Modifier = this diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt new file mode 100644 index 0000000000..5cdb61e42b --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt @@ -0,0 +1,26 @@ +/* + * 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.ui.components + +import androidx.compose.runtime.Composable + +@Composable +actual fun SetDialogToEdgeToEdge() {} From 04da017f3bb6c06eb7af8fa2ea7ae9f8e276741c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 13:30:05 +0000 Subject: [PATCH 11/20] refactor: move the NIP-46 signer screen into commonsUI (step 6 wave 6) Its Toasts are shared notices now, so it has no Android-only dependency left. The plan doc records wave 6 and names the upload stack as the next structural blocker. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/ui/navigation/AppNavigation.kt | 2 +- .../2026-09-27-one-ui-android-desktop.md | 22 +++++++++++++++---- .../settings/nip46/Nip46SignerScreen.kt | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt (99%) 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 db2db89973..ba81057a14 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 @@ -159,6 +159,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.Nip46SignerScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.SoftwareAppDetailScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.SoftwareAppsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.AddClinkDebitWalletScreen @@ -329,7 +330,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.UserSettingsScreen 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.settings.nip46.Nip46SignerScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.ShortsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.ThreadScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlPostScreen 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 2ea3bfda8f..095146935d 100644 --- a/commons/plans/2026-09-27-one-ui-android-desktop.md +++ b/commons/plans/2026-09-27-one-ui-android-desktop.md @@ -402,10 +402,24 @@ assemblers, `EventSync`, …). Packages are renamed on the way: - **Wave 5 moved:** 11 screens, 23 files: the wallet detail, send, receive and transaction screens; on-chain transactions; the Buzz canvas, agent console and DM list; badge awarding; old bookmarks; and the settings index. - - **Next:** re-measure. Known exits: the upload/gallery stack, the Android-only - `AccountViewModel` actions, `CalendarTimeFormat`, the chat views, `NestUiState`, - `NappletLauncher`, `UpdateZapAmountDialog` (biometrics), the `java.time` pickers, and - the language list in user settings. + - **Wave 6 cuts:** + - The gallery picker is shared. `SelectedMedia` holds an `expect abstract class + MediaUri`, which is `actual typealias MediaUri = android.net.Uri` on Android, so the + upload code reading `media.uri` is unchanged. The picker launchers are + expect/actual; desktop and iOS report a cancel for now. + - Other expect/actuals: `JavaSerializable`, `imageContentReceiver` (image paste) and + `SetDialogToEdgeToEdge`. + - The editor's URL highlighting uses quartz's `UrlDetector`. + - `MediaMimeTypes` moved to commons. + - **Wave 6 moved:** the NIP-46 signer screen. The other cuts clear blockers that sit + behind the upload stack. + - **Next:** the post-composer upload stack is now the main blocker, holding about 30 + screens. `UploadOrchestrator`/`MultiOrchestrator` thread an Android `Context` through + compression, metadata stripping, encryption and the NIP-95/NIP-96/Blossom uploaders, + and the composer view models call them directly. The video player stack (media3) is + next at about 22. Smaller exits remain: the Android-only `AccountViewModel` actions, + `CalendarTimeFormat`, `NestUiState`, `NappletLauncher`, `UpdateZapAmountDialog` + (biometrics), the `java.time` pickers and the language list. 7. **Navigation**: the library swap, then `AppNavigation` + rail + drawer + bottom bar. 8. **The app root port** and the new JVM shim. Then the Desktop feature inventory, and retiring the old `desktopApp`. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt index 22e1ef4386..772d4c261f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/nip46/Nip46SignerScreen.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.nip46 +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.nip46 import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.RepeatMode From 2fd02a0753812f6567184b564d94e2d3e2e86e63 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 15:05:42 +0000 Subject: [PATCH 12/20] refactor: MediaUploader port for the upload pipeline Move the platform-neutral upload model into commons (commons.service.uploads): MediaUri, SelectedMedia, CompressorQuality, MediaCompressorResult, UploadError, UploadingState, UploadOrchestrator, MultiOrchestrator and the MediaUploader port. Error states carry an UploadError enum; commonsUI maps it to a string resource. The Android compress/strip/upload pipeline stays in the app as AndroidMediaUploader, reached through AccountViewModelHost.mediaUploader. Composer view models and screens pass the uploader instead of a Context. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../AvifUploadPipelineInstrumentedTest.kt | 1 + .../service/uploads/AndroidMediaUploader.kt | 592 +++++++++++++++++ .../service/uploads/MediaCompressor.kt | 28 +- .../service/uploads/UploadOrchestrator.kt | 602 ------------------ .../service/uploads/VideoCompressionHelper.kt | 2 + .../amethyst/ui/actions/EditPostView.kt | 5 +- .../amethyst/ui/actions/EditPostViewModel.kt | 52 +- .../amethyst/ui/actions/NewMediaModel.kt | 23 +- .../amethyst/ui/actions/NewMediaView.kt | 6 +- .../ui/actions/NewUserMetadataViewModel.kt | 4 +- .../ui/actions/uploads/SelectFromFiles.kt | 2 +- .../ui/actions/uploads/SharedMediaResolver.kt | 2 +- .../ui/actions/uploads/ShowImageUploadItem.kt | 10 +- .../ui/actions/uploads/TakePicture.kt | 2 +- .../uploads/UploadProgressIndicator.kt | 5 +- .../ui/components/ThinPaddingTextField.kt | 2 +- .../creators/uploads/ImageVideoDescription.kt | 4 +- .../nip22Comments/CommentPostViewModel.kt | 50 +- .../nip22Comments/GenericCommentPostScreen.kt | 4 +- .../ui/note/share/ShareNoteAsImageScreen.kt | 9 +- .../AccountViewModelAndroidActions.kt | 5 +- .../loggedIn/AndroidAccountViewModelHost.kt | 4 + .../loggedIn/badges/post/NewBadgeDialog.kt | 4 +- .../loggedIn/badges/post/NewBadgeModel.kt | 21 +- .../BookmarkGroupMetadataViewModel.kt | 4 +- .../create/NewCalendarEventScreen.kt | 2 +- .../create/NewCalendarEventViewModel.kt | 12 +- .../chats/cordnGroup/CordnGroupChatScreen.kt | 27 +- .../chats/marmotGroup/CreateGroupScreen.kt | 4 +- .../chats/marmotGroup/EditGroupInfoScreen.kt | 4 +- .../chats/marmotGroup/MarmotGroupChatView.kt | 3 +- .../marmotGroup/MarmotGroupIconEditor.kt | 2 +- .../marmotGroup/send/MarmotFileUploader.kt | 17 +- .../send/MarmotGroupIconUploader.kt | 20 +- .../send/MarmotNewMessageViewModel.kt | 2 +- .../loggedIn/chats/minichat/MinichatScreen.kt | 4 +- .../privateDM/send/ChatNewMessageViewModel.kt | 26 +- .../chats/privateDM/send/IMetaAttachments.kt | 2 +- .../chats/privateDM/send/NewGroupDMScreen.kt | 4 +- .../send/PrivateMessageEditFieldRow.kt | 4 +- .../privateDM/send/upload/ChatFileSender.kt | 2 +- .../send/upload/ChatFileUploadDialog.kt | 4 +- .../privateDM/send/upload/ChatFileUploader.kt | 42 +- .../send/upload/SuccessfulUploads.kt | 2 +- .../concord/ConcordChannelScreen.kt | 4 +- .../concord/ConcordImageUploader.kt | 11 +- .../concord/ConcordMetadataForm.kt | 4 +- .../send/ConcordNewMessageViewModel.kt | 2 +- .../metadata/ChannelMetadataViewModel.kt | 4 +- .../relayGroup/RelayGroupMetadataViewModel.kt | 4 +- .../send/ChannelFileUploadDialog.kt | 4 +- .../send/ChannelNewMessageViewModel.kt | 30 +- .../chats/publicChannels/send/EditFieldRow.kt | 2 +- .../chats/utils/ChatFileUploadState.kt | 6 +- .../newCommunity/NewCommunityModel.kt | 25 +- .../newCommunity/NewCommunityScreen.kt | 6 +- .../nip23LongForm/LongFormPostScreen.kt | 8 +- .../nip23LongForm/LongFormPostViewModel.kt | 117 ++-- .../nip99Classifieds/NewProductScreen.kt | 4 +- .../nip99Classifieds/NewProductViewModel.kt | 44 +- .../emojipacks/display/EmojiPackViewModel.kt | 4 +- .../metadata/EmojiPackMetadataViewModel.kt | 4 +- .../geocaches/create/NewGeocacheScreen.kt | 4 +- .../geocaches/create/NewGeocacheViewModel.kt | 12 +- .../geocaches/log/LogGeocacheFindScreen.kt | 4 +- .../geocaches/log/LogGeocacheFindViewModel.kt | 12 +- .../loggedIn/geohash/GeoHashPostScreen.kt | 2 +- .../loggedIn/hashtag/HashtagPostScreen.kt | 2 +- .../loggedIn/home/ShortNotePostScreen.kt | 4 +- .../loggedIn/home/ShortNotePostViewModel.kt | 54 +- .../loggedIn/home/VoiceReplyViewModel.kt | 8 +- .../metadata/FollowPackMetadataViewModel.kt | 4 +- .../metadata/PeopleListMetadataViewModel.kt | 4 +- .../loggedIn/longs/NewLongVideoButton.kt | 2 +- .../loggedIn/music/MusicComposerUploadUi.kt | 2 +- .../music/NewMusicPlaylistViewModel.kt | 26 +- .../loggedIn/music/NewMusicTrackScreen.kt | 2 +- .../loggedIn/music/NewMusicTrackViewModel.kt | 27 +- .../nests/create/CreateNestViewModel.kt | 4 +- .../nests/room/chat/NestEditFieldRow.kt | 2 +- .../nests/room/chat/NestFileUploadDialog.kt | 4 +- .../room/chat/NestNewMessageViewModel.kt | 30 +- .../publicMessages/NewPublicMessageScreen.kt | 6 +- .../NewPublicMessageViewModel.kt | 50 +- .../loggedIn/pictures/NewPictureButton.kt | 2 +- .../authoring/EditPodcastShowViewModel.kt | 15 +- .../authoring/NewPodcastEpisodeScreen.kt | 2 +- .../authoring/NewPodcastEpisodeViewModel.kt | 15 +- .../authoring/NewPodcastTrailerScreen.kt | 2 +- .../authoring/NewPodcastTrailerViewModel.kt | 10 +- .../authoring/PodcastComposerMedia.kt | 21 +- .../loggedIn/shorts/NewShortVideoButton.kt | 2 +- .../ui/screen/loggedIn/url/UrlPostScreen.kt | 2 +- .../loggedIn/video/NewVideoFeedButton.kt | 2 +- .../ui/actions/uploads/SelectedMediaTest.kt | 2 +- .../ui/components/MediaCompressorTest.kt | 2 +- .../service}/uploads/MediaUri.android.kt | 2 +- .../service/uploads/CompressorQuality.kt | 43 ++ .../service/uploads/MediaCompressorResult.kt | 28 + .../commons/service/uploads/MediaUploader.kt | 124 ++++ .../commons/service}/uploads/MediaUri.kt | 2 +- .../service/uploads/MultiOrchestrator.kt | 14 +- .../commons/service/uploads/SelectedMedia.kt | 65 ++ .../uploads/SelectedMediaProcessing.kt | 5 +- .../commons/service/uploads/UploadError.kt | 35 + .../service/uploads/UploadOrchestrator.kt | 121 ++++ .../commons/service/uploads/UploadingState.kt | 46 ++ .../viewmodels/AccountViewModelHost.kt | 4 + .../commons/service}/uploads/MediaUri.ios.kt | 2 +- .../commons/service}/uploads/MediaUri.jvm.kt | 2 +- .../actions/uploads/GallerySelect.android.kt | 1 + .../ImageContentReceiver.android.kt | 2 +- .../ui/actions/uploads/SelectFromGallery.kt | 45 +- .../ui/components/ImageContentReceiver.kt | 2 +- .../commons/ui/uploads/UploadErrorText.kt | 52 ++ .../ui/actions/uploads/GallerySelect.ios.kt | 1 + .../ui/components/ImageContentReceiver.ios.kt | 2 +- .../AccountViewModelPreviews.jvmAndroid.kt | 3 + .../ui/actions/uploads/GallerySelect.jvm.kt | 1 + .../ui/components/ImageContentReceiver.jvm.kt | 2 +- 120 files changed, 1658 insertions(+), 1197 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt delete mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt rename {commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions => commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service}/uploads/MediaUri.android.kt (94%) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/CompressorQuality.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaCompressorResult.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUploader.kt rename {commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions => commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service}/uploads/MediaUri.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/service/uploads/MultiOrchestrator.kt (94%) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/SelectedMedia.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions => commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service}/uploads/SelectedMediaProcessing.kt (86%) create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadError.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadOrchestrator.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadingState.kt rename {commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions => commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service}/uploads/MediaUri.ios.kt (95%) rename {commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions => commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service}/uploads/MediaUri.jvm.kt (95%) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadErrorText.kt diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt index 1cbcf9677a..44ae559b7a 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/service/uploads/AvifUploadPipelineInstrumentedTest.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.appContext import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.contentUriFor import com.vitorpamplona.amethyst.AvifInstrumentedTestSupport.copyAssetToCache import com.vitorpamplona.amethyst.commons.service.uploads.AVIF_MIME +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt new file mode 100644 index 0000000000..293b53d998 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt @@ -0,0 +1,592 @@ +/* + * 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.service.uploads + +import android.content.Context +import android.net.Uri +import com.vitorpamplona.amethyst.Amethyst +import com.vitorpamplona.amethyst.commons.model.Account +import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName +import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType +import com.vitorpamplona.amethyst.commons.model.mediaServers.blossomUploadOrder +import com.vitorpamplona.amethyst.commons.service.upload.BlossomClient +import com.vitorpamplona.amethyst.commons.service.upload.BlossomPaymentException +import com.vitorpamplona.amethyst.commons.service.upload.FileHeader +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaCompressorResult +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri +import com.vitorpamplona.amethyst.commons.service.uploads.UploadError +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState.UploadingFinalState +import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader +import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader +import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions +import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent +import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent +import com.vitorpamplona.quartz.nipB7Blossom.BlossomServerUrl +import com.vitorpamplona.quartz.utils.Log +import com.vitorpamplona.quartz.utils.ciphers.NostrCipher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import java.io.File +import kotlin.coroutines.cancellation.CancellationException + +/** + * Android's [MediaUploader]: compresses with LightCompressor/Compressor, strips metadata, encrypts, + * and uploads over the app's upload-role OkHttp clients. Reads files through the application + * [appContext]'s content resolver, so it needs no Activity. + */ +class AndroidMediaUploader( + private val appContext: Context, +) : MediaUploader { + override suspend fun upload( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + server: ServerName, + account: Account, + useH265: Boolean, + stripMetadata: Boolean, + onStrippingFailed: suspend () -> Boolean, + convertGifToMp4: Boolean, + forcedSigner: NostrSigner?, + ): UploadingFinalState = Job(progress).upload(uri, mimeType, alt, contentWarningReason, compressionQuality, server, account, appContext, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) + + override suspend fun uploadEncrypted( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + encrypt: NostrCipher, + server: ServerName, + account: Account, + useH265: Boolean, + stripMetadata: Boolean, + onStrippingFailed: suspend () -> Boolean, + convertGifToMp4: Boolean, + forcedSigner: NostrSigner?, + ): UploadingFinalState = Job(progress).uploadEncrypted(uri, mimeType, alt, contentWarningReason, compressionQuality, encrypt, server, account, appContext, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) + + override suspend fun compressIfNeeded( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + compressionQuality: CompressorQuality, + useH265: Boolean, + convertGifToMp4: Boolean, + ): MediaCompressorResult = Job(progress).compressIfNeeded(uri, mimeType, compressionQuality, appContext, useH265, convertGifToMp4) + + /** One upload's run: the pipeline, reporting into [progress]. */ + private class Job( + val progress: UploadOrchestrator, + ) { + fun error( + error: UploadError, + vararg params: String, + ) = progress.error(error, *params) + + fun finish(result: UploadOrchestrator.OrchestratorResult) = progress.finish(result) + + fun updateState( + newProgress: Double, + newState: UploadingState, + ) = progress.updateState(newProgress, newState) + + private fun uploadNIP95( + fileUri: Uri, + contentType: String?, + originalContentType: String?, + originalHash: String?, + context: Context, + ): UploadingFinalState { + updateState(0.4, UploadingState.Uploading) + + val bytes = + context.contentResolver.openInputStream(fileUri)?.use { + it.readBytes() + } + + if (bytes != null) { + if (bytes.size > 80000) { + return error(UploadError.MEDIA_TOO_BIG_FOR_NIP95) + } + + updateState(0.8, UploadingState.Hashing) + + val result = + FileHeader.prepare( + bytes, + contentType, + null, + ) + + result.fold( + onSuccess = { + return finish(UploadOrchestrator.OrchestratorResult.NIP95Result(it, bytes, originalContentType, originalHash)) + }, + onFailure = { + return error(UploadError.COULD_NOT_CHECK_DOWNLOADED_FILE, it.message ?: it.javaClass.simpleName) + }, + ) + } else { + return error(UploadError.COULD_NOT_OPEN_COMPRESSED_FILE) + } + } + + private suspend fun uploadNIP96( + fileUri: Uri, + contentType: String?, + size: Long?, + alt: String?, + contentWarningReason: String?, + serverBaseUrl: String, + contentTypeForResult: String?, + originalHash: String?, + account: Account, + forcedSigner: NostrSigner?, + context: Context, + ): UploadingFinalState { + updateState(0.2, UploadingState.Uploading) + return try { + val result = + Nip96Uploader().upload( + uri = fileUri, + contentType = contentType, + size = size, + alt = alt, + sensitiveContent = contentWarningReason, + serverBaseUrl = serverBaseUrl, + okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, + onProgress = { percent: Float -> + updateState(0.2 + (0.2 * percent), UploadingState.Uploading) + }, + httpAuth = + if (forcedSigner != null) { + { url, method, body -> forcedSigner.sign(HTTPAuthorizationEvent.build(url, method, body)) } + } else { + account::createHTTPAuthorization + }, + context = context, + ) + + verifyHeader( + uploadResult = result, + localContentType = contentType, + originalContentType = contentTypeForResult, + originalHash = originalHash, + okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, + ) + } catch (_: SignerExceptions.ReadOnlyException) { + error(UploadError.LOGIN_WITH_PRIVATE_KEY) + } catch (e: Exception) { + if (e is CancellationException) throw e + error(UploadError.FAILED_TO_UPLOAD_MEDIA, e.message ?: e.javaClass.simpleName) + } + } + + /** + * Tries [selected], then the rest of the servers the picker offered (see + * [blossomUploadOrder]) until one stores the blob. Every server gets the same blob, so they + * share one signed upload token: the signer is asked once, and a signer that refused, timed + * out or is read-only stops the fallback instead of prompting again per server. Attempts + * don't publish their errors; when every server fails, the error shown is the selected + * server's, the one the user chose and will recognize. + */ + private suspend fun uploadBlossomWithFallback( + selected: ServerName, + account: Account, + uploadTo: suspend (serverBaseUrl: String, auth: SharedUploadAuth) -> UploadingFinalState, + ): UploadingFinalState { + val order = blossomUploadOrder(selected, account.blossomServers.hostNameFlow.value) + val auth = SharedUploadAuth() + var firstError: UploadingState.Error? = null + for (server in order) { + when (val result = uploadTo(server.baseUrl, auth)) { + is UploadingState.Finished -> return result + is UploadingState.Error -> { + if (firstError == null) firstError = result + if (auth.signerFailed) return result.also { updateState(0.0, it) } + Log.w("UploadOrchestrator", "Upload to ${server.baseUrl} failed, trying the next server") + } + } + } + return firstError!!.also { updateState(0.0, it) } + } + + /** + * The upload token for one blob, signed at most once and reused by every server tried. + * A signer that returned no token is asked again on the next server; only a token or a + * signer failure is kept. + */ + private class SharedUploadAuth { + private var outcome: Result? = null + + var signerFailed = false + private set + + suspend fun get(sign: suspend () -> BlossomAuthorizationEvent?): BlossomAuthorizationEvent? { + outcome?.let { return it.getOrThrow() } + return try { + sign()?.also { outcome = Result.success(it) } + } catch (e: SignerExceptions) { + signerFailed = true + outcome = Result.failure(e) + throw e + } + } + } + + private suspend fun uploadBlossom( + fileUri: Uri, + contentType: String?, + size: Long?, + alt: String?, + contentWarningReason: String?, + serverBaseUrl: String, + contentTypeForResult: String?, + originalHash: String?, + account: Account, + forcedSigner: NostrSigner?, + context: Context, + sharedAuth: SharedUploadAuth, + ): UploadingFinalState { + updateState(0.2, UploadingState.Uploading) + // BUD-05: route through /media (optimize) when the user opted in. The forced-signer + // path (e.g. NIP-46 draft signing) always uses the bit-exact /upload. + val useMedia = forcedSigner == null && account.settings.optimizeMediaOnUpload.value + return try { + val result = + BlossomUploader() + .upload( + uri = fileUri, + contentType = contentType, + size = size, + alt = alt, + sensitiveContent = contentWarningReason, + serverBaseUrl = serverBaseUrl, + okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, + // Use a t=media token when optimizing via /media, otherwise a plain + // t=upload token. Tokens are intentionally NOT server-scoped on the + // upload path: some servers reject an upload whose auth carries a + // `server` tag, and upload-token replay is not the threat scoping + // guards against (delete tokens are — those stay scoped). + httpAuth = { hash, size, alt -> + sharedAuth.get { + when { + forcedSigner != null -> BlossomAuthorizationEvent.createUploadAuth(hash, size, alt, forcedSigner) + useMedia -> account.createBlossomMediaAuth(hash, size, alt) + else -> account.createBlossomUploadAuth(hash, size, alt) + } + } + }, + context = context, + useMediaEndpoint = useMedia, + ) + + val finalState = + verifyHeader( + uploadResult = result, + localContentType = contentType, + okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, + originalHash = originalHash, + originalContentType = contentTypeForResult, + ) + + // BUD-04: replicate the blob to the user's other Blossom servers for redundancy. + // Fire-and-forget on the account scope AFTER the upload is finished: mirroring is + // pure background redundancy, so it must never delay, alter, or fail the upload the + // user already completed, and must not touch the on-screen progress state. + if (finalState is UploadingState.Finished && forcedSigner == null && account.settings.mirrorUploadsToAllServers.value) { + account.scope.launch(Dispatchers.IO) { + try { + mirrorToOtherServers(result, serverBaseUrl, account) + } catch (e: Exception) { + if (e is CancellationException) throw e + Log.w("UploadOrchestrator", "Background mirror failed", e) + } + } + } + + finalState + } catch (_: SignerExceptions.ReadOnlyException) { + UploadingState.Error(UploadError.LOGIN_WITH_PRIVATE_KEY, emptyArray()) + } catch (e: BlossomPaymentException) { + // BUD-07: the server wants payment before it will store the blob. + UploadingState.Error(UploadError.BLOSSOM_PAYMENT_REQUIRED, arrayOf(e.payment.reason ?: serverBaseUrl)) + } catch (e: Exception) { + if (e is CancellationException) throw e + UploadingState.Error(UploadError.FAILED_TO_UPLOAD_MEDIA, arrayOf(e.message?.ifBlank { null } ?: e.javaClass.simpleName)) + } + } + + /** + * BUD-04 mirror fan-out: asks every *other* Blossom server in the account's + * kind-10063 list to pull the freshly-uploaded blob from [result]'s URL. Runs + * in the background after the upload is finished (see caller), so it never + * delays the user's post or touches the upload progress UI; per-server failures + * are swallowed. Requires the blob's sha256 so server B can verify the download. + */ + private suspend fun mirrorToOtherServers( + result: MediaUploadResult, + primaryServerBaseUrl: String, + account: Account, + ) { + val sourceUrl = result.url ?: return + val hash = result.sha256 ?: sourceUrl.substringAfterLast('/').substringBefore('.') + if (hash.length != 64) return + + // Only the user's *explicitly configured* kind-10063 servers (flow), NOT the + // DEFAULT_MEDIA_SERVERS fallback that hostNameFlow injects — we must never fan + // uploads out to public defaults the user never opted into. + val primaryDomain = BlossomServerUrl.domain(primaryServerBaseUrl) + val targets = + account.blossomServers.flow.value + .filter { BlossomServerUrl.domain(it) != primaryDomain } + .distinct() + + if (targets.isEmpty()) return + + val contentType = result.type ?: "application/octet-stream" + targets.forEach { target -> + try { + val auth = account.createBlossomUploadAuth(hash, result.size ?: 0L, "Mirror $hash").toAuthorizationHeader() + BlossomClient(Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForUploads(target)) + .mirrorOrUpload(sourceUrl, hash, contentType, target, auth) + } catch (e: Exception) { + if (e is CancellationException) throw e + Log.w("UploadOrchestrator", "Failed to mirror $hash to $target", e) + } + } + } + + private suspend fun verifyHeader( + uploadResult: MediaUploadResult, + localContentType: String?, + originalContentType: String?, + originalHash: String?, + okHttpClient: (String) -> OkHttpClient, + ): UploadingFinalState { + if (uploadResult.url.isNullOrBlank()) { + return error(UploadError.SERVER_DID_NOT_PROVIDE_URL) + } + + updateState(0.6, UploadingState.Downloading) + + // Use streaming verification for memory efficiency with large files + val verification = + ImageDownloader().waitAndVerifyStream(uploadResult.url, okHttpClient) + ?: return error(UploadError.COULD_NOT_DOWNLOAD_FROM_SERVER) + + updateState(0.8, UploadingState.Hashing) + + // Create FileHeader with hash from streaming verification + // Note: We skip blurhash/dimensions since we already have them from upload + val fileHeader = + FileHeader( + mimeType = uploadResult.type ?: localContentType ?: verification.contentType, + hash = verification.hash, + size = verification.size.toInt(), + dim = uploadResult.dimension, + blurHash = uploadResult.blurHash, + ) + + return finish( + UploadOrchestrator.OrchestratorResult.ServerResult( + fileHeader, + uploadResult.url, + uploadResult.magnet, + uploadResult.sha256, + originalContentType, + originalHash, + ), + ) + } + + suspend fun compressIfNeeded( + uri: Uri, + mimeType: String?, + compressionQuality: CompressorQuality, + context: Context, + useH265: Boolean = false, + convertGifToMp4: Boolean = false, + ) = if (compressionQuality != CompressorQuality.UNCOMPRESSED || convertGifToMp4) { + updateState(0.02, UploadingState.Compressing) + MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext, useH265, convertGifToMp4) + } else { + MediaCompressorResult(uri, mimeType, null) + } + + private suspend fun stripAfterCompression( + originalUri: Uri, + compressed: MediaCompressorResult, + mimeType: String?, + compressionQuality: CompressorQuality, + stripMetadata: Boolean, + onStrippingFailed: suspend () -> Boolean, + context: Context, + ): Uri? { + if (!stripMetadata) return compressed.uri + + val effectiveMimeType = compressed.contentType ?: mimeType + val isVideo = effectiveMimeType?.startsWith("video/", ignoreCase = true) == true + val compressionRequested = compressionQuality != CompressorQuality.UNCOMPRESSED + val compressionApplied = compressionRequested && compressed.uri != originalUri + + val strippingResult = + if (isVideo && compressionApplied) { + // Compression was requested and actually applied to a video; + // assume it stripped metadata successfully. + StrippingResult(compressed.uri, true) + } else { + // AvifMetadataNotVerifiableException is allowed to propagate so the caller + // can emit a specific error rather than the generic "Upload cancelled". + MetadataStripper.strip(compressed.uri, effectiveMimeType, context.applicationContext) + } + + if (!strippingResult.stripped && !onStrippingFailed()) return null + + return strippingResult.uri + } + + /** + * Deletes a temporary file created during the upload pipeline if its URI + * differs from the original (meaning it's an intermediate temp file, not the user's content). + */ + internal fun deleteTempUri( + tempUri: Uri, + originalUri: Uri, + ) { + if (tempUri == originalUri) return + try { + val path = tempUri.path ?: return + val file = File(path) + if (file.delete()) { + Log.d("UploadOrchestrator") { "Deleted temp file: $path" } + } + } catch (e: Exception) { + Log.w("UploadOrchestrator", "Failed to delete temp file: ${tempUri.path}", e) + } + } + + suspend fun upload( + uri: Uri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + server: ServerName, + account: Account, + context: Context, + useH265: Boolean = false, + stripMetadata: Boolean = true, + onStrippingFailed: suspend () -> Boolean = { true }, + convertGifToMp4: Boolean = false, + forcedSigner: NostrSigner? = null, + ): UploadingFinalState { + val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265, convertGifToMp4) + + val finalUri = + try { + stripAfterCompression(uri, compressed, mimeType, compressionQuality, stripMetadata, onStrippingFailed, context) + } catch (e: AvifMetadataNotVerifiableException) { + return error(UploadError.AVIF_METADATA_STRIP_FAILED, e.message ?: e.javaClass.simpleName).also { + deleteTempUri(compressed.uri, uri) + } + } ?: return error(UploadError.UPLOAD_CANCELLED).also { + deleteTempUri(compressed.uri, uri) + } + + if (compressed.uri != finalUri) deleteTempUri(compressed.uri, uri) + + try { + return when (server.type) { + ServerType.NIP95 -> uploadNIP95(finalUri, compressed.contentType, null, null, context) + ServerType.NIP96 -> uploadNIP96(finalUri, compressed.contentType, compressed.size, alt, contentWarningReason, server.baseUrl, null, null, account, forcedSigner, context) + ServerType.Blossom -> + uploadBlossomWithFallback(server, account) { baseUrl, auth -> + uploadBlossom(finalUri, compressed.contentType, compressed.size, alt, contentWarningReason, baseUrl, null, null, account, forcedSigner, context, auth) + } + } + } finally { + deleteTempUri(finalUri, uri) + } + } + + suspend fun uploadEncrypted( + uri: Uri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + encrypt: NostrCipher, + server: ServerName, + account: Account, + context: Context, + useH265: Boolean = false, + stripMetadata: Boolean = true, + onStrippingFailed: suspend () -> Boolean = { true }, + convertGifToMp4: Boolean = false, + forcedSigner: NostrSigner? = null, + ): UploadingFinalState { + val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265, convertGifToMp4) + + val finalUri = + try { + stripAfterCompression(uri, compressed, mimeType, compressionQuality, stripMetadata, onStrippingFailed, context) + } catch (e: AvifMetadataNotVerifiableException) { + return error(UploadError.AVIF_METADATA_STRIP_FAILED, e.message ?: e.javaClass.simpleName).also { + deleteTempUri(compressed.uri, uri) + } + } ?: return error(UploadError.UPLOAD_CANCELLED).also { + deleteTempUri(compressed.uri, uri) + } + + if (compressed.uri != finalUri) deleteTempUri(compressed.uri, uri) + + val encrypted = EncryptFiles().encryptFile(context, finalUri, encrypt) + deleteTempUri(finalUri, uri) + + try { + return when (server.type) { + ServerType.NIP95 -> uploadNIP95(encrypted.uri, encrypted.contentType, compressed.contentType, encrypted.originalHash, context) + ServerType.NIP96 -> uploadNIP96(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, server.baseUrl, compressed.contentType, encrypted.originalHash, account, forcedSigner, context) + // The same encrypted file goes to every server tried, so its key, nonce and + // hash stay valid whichever one ends up holding it. + ServerType.Blossom -> + uploadBlossomWithFallback(server, account) { baseUrl, auth -> + uploadBlossom(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, baseUrl, compressed.contentType, encrypted.originalHash, account, forcedSigner, context, auth) + } + } + } finally { + deleteTempUri(encrypted.uri, uri) + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt index c36b42ed1d..d2fa109863 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt @@ -26,6 +26,8 @@ import android.net.Uri import androidx.core.net.toUri import androidx.media3.common.MimeTypes import com.davotoula.lightcompressor.video.GifToMp4Converter +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaCompressorResult import com.vitorpamplona.amethyst.commons.service.uploads.isAvif import com.vitorpamplona.amethyst.ui.components.util.MediaCompressorFileUtils import com.vitorpamplona.quartz.utils.Log @@ -34,12 +36,6 @@ import id.zelory.compressor.constraint.default import kotlinx.coroutines.CancellationException import java.io.File -class MediaCompressorResult( - val uri: Uri, - val contentType: String?, - val size: Long?, -) - class MediaCompressor { // ALL ERRORS ARE IGNORED. The original file is returned. suspend fun compress( @@ -125,24 +121,4 @@ class MediaCompressor { MediaCompressorResult(uri, contentType, null) } } - - companion object { - fun intToCompressorQuality(mediaQualityFloat: Int): CompressorQuality = - when (mediaQualityFloat) { - 0 -> CompressorQuality.LOW - 1 -> CompressorQuality.MEDIUM - 2 -> CompressorQuality.HIGH - 3 -> CompressorQuality.UNCOMPRESSED - else -> CompressorQuality.MEDIUM - } - } -} - -enum class CompressorQuality { - VERY_LOW, - LOW, - MEDIUM, - HIGH, - VERY_HIGH, - UNCOMPRESSED, } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt deleted file mode 100644 index af41342da5..0000000000 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt +++ /dev/null @@ -1,602 +0,0 @@ -/* - * 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.service.uploads - -import android.content.Context -import android.net.Uri -import com.vitorpamplona.amethyst.Amethyst -import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName -import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType -import com.vitorpamplona.amethyst.commons.model.mediaServers.blossomUploadOrder -import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed -import com.vitorpamplona.amethyst.commons.resources.blossom_payment_required -import com.vitorpamplona.amethyst.commons.resources.could_not_check_downloaded_file -import com.vitorpamplona.amethyst.commons.resources.could_not_download_from_the_server -import com.vitorpamplona.amethyst.commons.resources.could_not_open_the_compressed_file -import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media -import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_upload -import com.vitorpamplona.amethyst.commons.resources.media_too_big_for_nip95 -import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.resources.upload_cancelled -import com.vitorpamplona.amethyst.commons.service.upload.BlossomClient -import com.vitorpamplona.amethyst.commons.service.upload.BlossomPaymentException -import com.vitorpamplona.amethyst.commons.service.upload.FileHeader -import com.vitorpamplona.amethyst.service.uploads.UploadingState.UploadingFinalState -import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader -import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner -import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions -import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent -import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent -import com.vitorpamplona.quartz.nipB7Blossom.BlossomServerUrl -import com.vitorpamplona.quartz.utils.Log -import com.vitorpamplona.quartz.utils.ciphers.NostrCipher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch -import okhttp3.OkHttpClient -import org.jetbrains.compose.resources.StringResource -import java.io.File -import kotlin.coroutines.cancellation.CancellationException - -sealed class UploadingState { - data object Ready : UploadingState() - - data object Compressing : UploadingState() - - data object Uploading : UploadingState() - - data object ServerProcessing : UploadingState() - - data object Downloading : UploadingState() - - data object Hashing : UploadingState() - - sealed class UploadingFinalState : UploadingState() - - class Finished( - val result: UploadOrchestrator.OrchestratorResult, - ) : UploadingFinalState() - - class Error( - val errorResource: StringResource, - val params: Array, - ) : UploadingFinalState() -} - -class UploadOrchestrator { - val progress = MutableStateFlow(0.0) - val progressState = MutableStateFlow(UploadingState.Ready) - - val isUploading = - progressState.map { - progressState.value !is UploadingState.Ready && progressState.value !is UploadingState.Error && progressState.value !is UploadingState.Finished - } - - fun error( - resId: StringResource, - vararg params: String, - ) = UploadingState.Error(resId, params).also { updateState(0.0, it) } - - fun finish(result: OrchestratorResult) = - UploadingState - .Finished(result) - .also { updateState(1.0, it) } - - fun updateState( - newProgress: Double, - newState: UploadingState, - ) { - progress.value = newProgress - progressState.value = newState - } - - private fun uploadNIP95( - fileUri: Uri, - contentType: String?, - originalContentType: String?, - originalHash: String?, - context: Context, - ): UploadingFinalState { - updateState(0.4, UploadingState.Uploading) - - val bytes = - context.contentResolver.openInputStream(fileUri)?.use { - it.readBytes() - } - - if (bytes != null) { - if (bytes.size > 80000) { - return error(Res.string.media_too_big_for_nip95) - } - - updateState(0.8, UploadingState.Hashing) - - val result = - FileHeader.prepare( - bytes, - contentType, - null, - ) - - result.fold( - onSuccess = { - return finish(OrchestratorResult.NIP95Result(it, bytes, originalContentType, originalHash)) - }, - onFailure = { - return error(Res.string.could_not_check_downloaded_file, it.message ?: it.javaClass.simpleName) - }, - ) - } else { - return error(Res.string.could_not_open_the_compressed_file) - } - } - - private suspend fun uploadNIP96( - fileUri: Uri, - contentType: String?, - size: Long?, - alt: String?, - contentWarningReason: String?, - serverBaseUrl: String, - contentTypeForResult: String?, - originalHash: String?, - account: Account, - forcedSigner: NostrSigner?, - context: Context, - ): UploadingFinalState { - updateState(0.2, UploadingState.Uploading) - return try { - val result = - Nip96Uploader().upload( - uri = fileUri, - contentType = contentType, - size = size, - alt = alt, - sensitiveContent = contentWarningReason, - serverBaseUrl = serverBaseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - onProgress = { percent: Float -> - updateState(0.2 + (0.2 * percent), UploadingState.Uploading) - }, - httpAuth = - if (forcedSigner != null) { - { url, method, body -> forcedSigner.sign(HTTPAuthorizationEvent.build(url, method, body)) } - } else { - account::createHTTPAuthorization - }, - context = context, - ) - - verifyHeader( - uploadResult = result, - localContentType = contentType, - originalContentType = contentTypeForResult, - originalHash = originalHash, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - ) - } catch (_: SignerExceptions.ReadOnlyException) { - error(Res.string.login_with_a_private_key_to_be_able_to_upload) - } catch (e: Exception) { - if (e is CancellationException) throw e - error(Res.string.failed_to_upload_media, e.message ?: e.javaClass.simpleName) - } - } - - /** - * Tries [selected], then the rest of the servers the picker offered (see - * [blossomUploadOrder]) until one stores the blob. Every server gets the same blob, so they - * share one signed upload token: the signer is asked once, and a signer that refused, timed - * out or is read-only stops the fallback instead of prompting again per server. Attempts - * don't publish their errors; when every server fails, the error shown is the selected - * server's, the one the user chose and will recognize. - */ - private suspend fun uploadBlossomWithFallback( - selected: ServerName, - account: Account, - uploadTo: suspend (serverBaseUrl: String, auth: SharedUploadAuth) -> UploadingFinalState, - ): UploadingFinalState { - val order = blossomUploadOrder(selected, account.blossomServers.hostNameFlow.value) - val auth = SharedUploadAuth() - var firstError: UploadingState.Error? = null - for (server in order) { - when (val result = uploadTo(server.baseUrl, auth)) { - is UploadingState.Finished -> return result - is UploadingState.Error -> { - if (firstError == null) firstError = result - if (auth.signerFailed) return result.also { updateState(0.0, it) } - Log.w("UploadOrchestrator", "Upload to ${server.baseUrl} failed, trying the next server") - } - } - } - return firstError!!.also { updateState(0.0, it) } - } - - /** - * The upload token for one blob, signed at most once and reused by every server tried. - * A signer that returned no token is asked again on the next server; only a token or a - * signer failure is kept. - */ - private class SharedUploadAuth { - private var outcome: Result? = null - - var signerFailed = false - private set - - suspend fun get(sign: suspend () -> BlossomAuthorizationEvent?): BlossomAuthorizationEvent? { - outcome?.let { return it.getOrThrow() } - return try { - sign()?.also { outcome = Result.success(it) } - } catch (e: SignerExceptions) { - signerFailed = true - outcome = Result.failure(e) - throw e - } - } - } - - private suspend fun uploadBlossom( - fileUri: Uri, - contentType: String?, - size: Long?, - alt: String?, - contentWarningReason: String?, - serverBaseUrl: String, - contentTypeForResult: String?, - originalHash: String?, - account: Account, - forcedSigner: NostrSigner?, - context: Context, - sharedAuth: SharedUploadAuth, - ): UploadingFinalState { - updateState(0.2, UploadingState.Uploading) - // BUD-05: route through /media (optimize) when the user opted in. The forced-signer - // path (e.g. NIP-46 draft signing) always uses the bit-exact /upload. - val useMedia = forcedSigner == null && account.settings.optimizeMediaOnUpload.value - return try { - val result = - BlossomUploader() - .upload( - uri = fileUri, - contentType = contentType, - size = size, - alt = alt, - sensitiveContent = contentWarningReason, - serverBaseUrl = serverBaseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - // Use a t=media token when optimizing via /media, otherwise a plain - // t=upload token. Tokens are intentionally NOT server-scoped on the - // upload path: some servers reject an upload whose auth carries a - // `server` tag, and upload-token replay is not the threat scoping - // guards against (delete tokens are — those stay scoped). - httpAuth = { hash, size, alt -> - sharedAuth.get { - when { - forcedSigner != null -> BlossomAuthorizationEvent.createUploadAuth(hash, size, alt, forcedSigner) - useMedia -> account.createBlossomMediaAuth(hash, size, alt) - else -> account.createBlossomUploadAuth(hash, size, alt) - } - } - }, - context = context, - useMediaEndpoint = useMedia, - ) - - val finalState = - verifyHeader( - uploadResult = result, - localContentType = contentType, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - originalHash = originalHash, - originalContentType = contentTypeForResult, - ) - - // BUD-04: replicate the blob to the user's other Blossom servers for redundancy. - // Fire-and-forget on the account scope AFTER the upload is finished: mirroring is - // pure background redundancy, so it must never delay, alter, or fail the upload the - // user already completed, and must not touch the on-screen progress state. - if (finalState is UploadingState.Finished && forcedSigner == null && account.settings.mirrorUploadsToAllServers.value) { - account.scope.launch(Dispatchers.IO) { - try { - mirrorToOtherServers(result, serverBaseUrl, account) - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.w("UploadOrchestrator", "Background mirror failed", e) - } - } - } - - finalState - } catch (_: SignerExceptions.ReadOnlyException) { - UploadingState.Error(Res.string.login_with_a_private_key_to_be_able_to_upload, emptyArray()) - } catch (e: BlossomPaymentException) { - // BUD-07: the server wants payment before it will store the blob. - UploadingState.Error(Res.string.blossom_payment_required, arrayOf(e.payment.reason ?: serverBaseUrl)) - } catch (e: Exception) { - if (e is CancellationException) throw e - UploadingState.Error(Res.string.failed_to_upload_media, arrayOf(e.message?.ifBlank { null } ?: e.javaClass.simpleName)) - } - } - - /** - * BUD-04 mirror fan-out: asks every *other* Blossom server in the account's - * kind-10063 list to pull the freshly-uploaded blob from [result]'s URL. Runs - * in the background after the upload is finished (see caller), so it never - * delays the user's post or touches the upload progress UI; per-server failures - * are swallowed. Requires the blob's sha256 so server B can verify the download. - */ - private suspend fun mirrorToOtherServers( - result: MediaUploadResult, - primaryServerBaseUrl: String, - account: Account, - ) { - val sourceUrl = result.url ?: return - val hash = result.sha256 ?: sourceUrl.substringAfterLast('/').substringBefore('.') - if (hash.length != 64) return - - // Only the user's *explicitly configured* kind-10063 servers (flow), NOT the - // DEFAULT_MEDIA_SERVERS fallback that hostNameFlow injects — we must never fan - // uploads out to public defaults the user never opted into. - val primaryDomain = BlossomServerUrl.domain(primaryServerBaseUrl) - val targets = - account.blossomServers.flow.value - .filter { BlossomServerUrl.domain(it) != primaryDomain } - .distinct() - - if (targets.isEmpty()) return - - val contentType = result.type ?: "application/octet-stream" - targets.forEach { target -> - try { - val auth = account.createBlossomUploadAuth(hash, result.size ?: 0L, "Mirror $hash").toAuthorizationHeader() - BlossomClient(Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForUploads(target)) - .mirrorOrUpload(sourceUrl, hash, contentType, target, auth) - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.w("UploadOrchestrator", "Failed to mirror $hash to $target", e) - } - } - } - - private suspend fun verifyHeader( - uploadResult: MediaUploadResult, - localContentType: String?, - originalContentType: String?, - originalHash: String?, - okHttpClient: (String) -> OkHttpClient, - ): UploadingFinalState { - if (uploadResult.url.isNullOrBlank()) { - return error(Res.string.server_did_not_provide_a_url_after_uploading) - } - - updateState(0.6, UploadingState.Downloading) - - // Use streaming verification for memory efficiency with large files - val verification = - ImageDownloader().waitAndVerifyStream(uploadResult.url, okHttpClient) - ?: return error(Res.string.could_not_download_from_the_server) - - updateState(0.8, UploadingState.Hashing) - - // Create FileHeader with hash from streaming verification - // Note: We skip blurhash/dimensions since we already have them from upload - val fileHeader = - FileHeader( - mimeType = uploadResult.type ?: localContentType ?: verification.contentType, - hash = verification.hash, - size = verification.size.toInt(), - dim = uploadResult.dimension, - blurHash = uploadResult.blurHash, - ) - - return finish( - OrchestratorResult.ServerResult( - fileHeader, - uploadResult.url, - uploadResult.magnet, - uploadResult.sha256, - originalContentType, - originalHash, - ), - ) - } - - sealed class OrchestratorResult { - class NIP95Result( - val fileHeader: FileHeader, - val bytes: ByteArray, - val mimeTypeBeforeEncryption: String?, - val hashBeforeEncryption: String?, - ) : OrchestratorResult() - - class ServerResult( - val fileHeader: FileHeader, - val url: String, - val magnet: String?, - val uploadedHash: String?, - val mimeTypeBeforeEncryption: String?, - val hashBeforeEncryption: String?, - ) : OrchestratorResult() - } - - suspend fun compressIfNeeded( - uri: Uri, - mimeType: String?, - compressionQuality: CompressorQuality, - context: Context, - useH265: Boolean = false, - convertGifToMp4: Boolean = false, - ) = if (compressionQuality != CompressorQuality.UNCOMPRESSED || convertGifToMp4) { - updateState(0.02, UploadingState.Compressing) - MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext, useH265, convertGifToMp4) - } else { - MediaCompressorResult(uri, mimeType, null) - } - - private suspend fun stripAfterCompression( - originalUri: Uri, - compressed: MediaCompressorResult, - mimeType: String?, - compressionQuality: CompressorQuality, - stripMetadata: Boolean, - onStrippingFailed: suspend () -> Boolean, - context: Context, - ): Uri? { - if (!stripMetadata) return compressed.uri - - val effectiveMimeType = compressed.contentType ?: mimeType - val isVideo = effectiveMimeType?.startsWith("video/", ignoreCase = true) == true - val compressionRequested = compressionQuality != CompressorQuality.UNCOMPRESSED - val compressionApplied = compressionRequested && compressed.uri != originalUri - - val strippingResult = - if (isVideo && compressionApplied) { - // Compression was requested and actually applied to a video; - // assume it stripped metadata successfully. - StrippingResult(compressed.uri, true) - } else { - // AvifMetadataNotVerifiableException is allowed to propagate so the caller - // can emit a specific error rather than the generic "Upload cancelled". - MetadataStripper.strip(compressed.uri, effectiveMimeType, context.applicationContext) - } - - if (!strippingResult.stripped && !onStrippingFailed()) return null - - return strippingResult.uri - } - - /** - * Deletes a temporary file created during the upload pipeline if its URI - * differs from the original (meaning it's an intermediate temp file, not the user's content). - */ - internal fun deleteTempUri( - tempUri: Uri, - originalUri: Uri, - ) { - if (tempUri == originalUri) return - try { - val path = tempUri.path ?: return - val file = File(path) - if (file.delete()) { - Log.d("UploadOrchestrator") { "Deleted temp file: $path" } - } - } catch (e: Exception) { - Log.w("UploadOrchestrator", "Failed to delete temp file: ${tempUri.path}", e) - } - } - - suspend fun upload( - uri: Uri, - mimeType: String?, - alt: String?, - contentWarningReason: String?, - compressionQuality: CompressorQuality, - server: ServerName, - account: Account, - context: Context, - useH265: Boolean = false, - stripMetadata: Boolean = true, - onStrippingFailed: suspend () -> Boolean = { true }, - convertGifToMp4: Boolean = false, - forcedSigner: NostrSigner? = null, - ): UploadingFinalState { - val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265, convertGifToMp4) - - val finalUri = - try { - stripAfterCompression(uri, compressed, mimeType, compressionQuality, stripMetadata, onStrippingFailed, context) - } catch (e: AvifMetadataNotVerifiableException) { - return error(Res.string.avif_metadata_strip_failed, e.message ?: e.javaClass.simpleName).also { - deleteTempUri(compressed.uri, uri) - } - } ?: return error(Res.string.upload_cancelled).also { - deleteTempUri(compressed.uri, uri) - } - - if (compressed.uri != finalUri) deleteTempUri(compressed.uri, uri) - - try { - return when (server.type) { - ServerType.NIP95 -> uploadNIP95(finalUri, compressed.contentType, null, null, context) - ServerType.NIP96 -> uploadNIP96(finalUri, compressed.contentType, compressed.size, alt, contentWarningReason, server.baseUrl, null, null, account, forcedSigner, context) - ServerType.Blossom -> - uploadBlossomWithFallback(server, account) { baseUrl, auth -> - uploadBlossom(finalUri, compressed.contentType, compressed.size, alt, contentWarningReason, baseUrl, null, null, account, forcedSigner, context, auth) - } - } - } finally { - deleteTempUri(finalUri, uri) - } - } - - suspend fun uploadEncrypted( - uri: Uri, - mimeType: String?, - alt: String?, - contentWarningReason: String?, - compressionQuality: CompressorQuality, - encrypt: NostrCipher, - server: ServerName, - account: Account, - context: Context, - useH265: Boolean = false, - stripMetadata: Boolean = true, - onStrippingFailed: suspend () -> Boolean = { true }, - convertGifToMp4: Boolean = false, - forcedSigner: NostrSigner? = null, - ): UploadingFinalState { - val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265, convertGifToMp4) - - val finalUri = - try { - stripAfterCompression(uri, compressed, mimeType, compressionQuality, stripMetadata, onStrippingFailed, context) - } catch (e: AvifMetadataNotVerifiableException) { - return error(Res.string.avif_metadata_strip_failed, e.message ?: e.javaClass.simpleName).also { - deleteTempUri(compressed.uri, uri) - } - } ?: return error(Res.string.upload_cancelled).also { - deleteTempUri(compressed.uri, uri) - } - - if (compressed.uri != finalUri) deleteTempUri(compressed.uri, uri) - - val encrypted = EncryptFiles().encryptFile(context, finalUri, encrypt) - deleteTempUri(finalUri, uri) - - try { - return when (server.type) { - ServerType.NIP95 -> uploadNIP95(encrypted.uri, encrypted.contentType, compressed.contentType, encrypted.originalHash, context) - ServerType.NIP96 -> uploadNIP96(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, server.baseUrl, compressed.contentType, encrypted.originalHash, account, forcedSigner, context) - // The same encrypted file goes to every server tried, so its key, nonce and - // hash stay valid whichever one ends up holding it. - ServerType.Blossom -> - uploadBlossomWithFallback(server, account) { baseUrl, auth -> - uploadBlossom(encrypted.uri, encrypted.contentType, encrypted.size, alt, contentWarningReason, baseUrl, compressed.contentType, encrypted.originalHash, account, forcedSigner, context, auth) - } - } - } finally { - deleteTempUri(encrypted.uri, uri) - } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt index 8b9a7ef3c0..615e23e5a9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt @@ -33,6 +33,8 @@ import com.davotoula.lightcompressor.VideoCompressor import com.davotoula.lightcompressor.config.AppSpecificStorageConfiguration import com.davotoula.lightcompressor.config.Configuration import com.davotoula.lightcompressor.config.VideoResizer +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaCompressorResult import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.LogLevel import kotlinx.coroutines.suspendCancellableCoroutine diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index bf256fd3e1..0cee50de61 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -59,7 +59,6 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextDirection @@ -122,8 +121,6 @@ fun EditPostView( val postViewModel: EditPostViewModel = viewModel() postViewModel.init(accountViewModel) - val context = LocalContext.current - val scrollState = rememberScrollState() val scope = rememberCoroutineScope() @@ -280,7 +277,7 @@ fun EditPostView( accountViewModel.account.settings.defaultFileServer, isUploading = postViewModel.mediaUploadTracker.isUploading, onAdd = { alt, server, sensitiveContent, mediaQuality, _, stripMetadata, _ -> - postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context, stripMetadata) + postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, accountViewModel.host.mediaUploader, stripMetadata) accountViewModel.account.settings.changeDefaultFileServer(server) }, onDelete = postViewModel::deleteMediaToUpload, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt index bd80fe5d5d..68377fd17c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.actions -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd import androidx.compose.runtime.Stable @@ -41,17 +40,19 @@ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent @@ -170,11 +171,11 @@ open class EditPostViewModel : ViewModel() { isPrivate: Boolean = false, server: ServerName, onError: (String, String) -> Unit, - context: Context, + uploader: MediaUploader, stripMetadata: Boolean = true, ) { try { - uploadUnsafe(alt, sensitiveContent, mediaQuality, isPrivate, server, onError, context, stripMetadata) + uploadUnsafe(alt, sensitiveContent, mediaQuality, isPrivate, server, onError, uploader, stripMetadata) } catch (e: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -192,7 +193,7 @@ open class EditPostViewModel : ViewModel() { isPrivate: Boolean = false, server: ServerName, onError: (String, String) -> Unit, - context: Context, + uploader: MediaUploader, stripMetadata: Boolean = true, ) { accountViewModel.launchSigner { @@ -206,10 +207,10 @@ open class EditPostViewModel : ViewModel() { myMultiOrchestrator.upload( alt, if (sensitiveContent) "" else null, - MediaCompressor.intToCompressorQuality(mediaQuality), + CompressorQuality.fromSlider(mediaQuality), server, myAccount, - context, + uploader, useH265Codec, stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, @@ -218,11 +219,12 @@ open class EditPostViewModel : ViewModel() { if (results.allGood) { val urls = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.NIP95Result) { val nip95 = myAccount.createNip95( - byteArray = state.result.bytes, - headerInfo = state.result.fileHeader, + byteArray = uploaded.bytes, + headerInfo = uploaded.fileHeader, alt = alt, contentWarningReason = if (sensitiveContent) "" else null, ) @@ -232,29 +234,29 @@ open class EditPostViewModel : ViewModel() { note?.let { "nostr:" + it.toNEvent() } - } else if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { + } else if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { val iMeta = - IMetaTagBuilder(state.result.url) + IMetaTagBuilder(uploaded.url) .apply { - hash(state.result.fileHeader.hash) - size(state.result.fileHeader.size) - state.result.fileHeader.mimeType + hash(uploaded.fileHeader.hash) + size(uploaded.fileHeader.size) + uploaded.fileHeader.mimeType ?.let { mimeType(it) } - state.result.fileHeader.dim + uploaded.fileHeader.dim ?.let { dims(it) } - state.result.fileHeader.blurHash + uploaded.fileHeader.blurHash ?.let { blurhash(it.blurhash) } - state.result.fileHeader.thumbHash + uploaded.fileHeader.thumbHash ?.let { thumbhash(it.thumbhash) } - state.result.magnet?.let { magnet(it) } - state.result.uploadedHash?.let { originalHash(it) } + uploaded.magnet?.let { magnet(it) } + uploaded.uploadedHash?.let { originalHash(it) } alt?.let { alt(it) } if (sensitiveContent) sensitiveContent("") }.build() iMetaAttachments = iMetaAttachments.filter { it.url != iMeta.url } + iMeta - state.result.url + uploaded.url } else { null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index 530db071bf..9ee3618759 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.actions -import android.content.Context import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -38,13 +37,15 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.Dispatchers @@ -100,13 +101,13 @@ open class NewMediaModel : ViewModel() { ): Boolean = mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(url) fun upload( - context: Context, + uploader: MediaUploader, accountViewModel: AccountViewModel, onSucess: () -> Unit, onError: (String, String) -> Unit, ) { try { - uploadUnsafe(context, accountViewModel, onSucess, onError) + uploadUnsafe(uploader, accountViewModel, onSucess, onError) } catch (e: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -118,7 +119,7 @@ open class NewMediaModel : ViewModel() { } fun uploadUnsafe( - context: Context, + uploader: MediaUploader, accountViewModel: AccountViewModel, onSucess: () -> Unit, onError: (String, String) -> Unit, @@ -135,10 +136,10 @@ open class NewMediaModel : ViewModel() { myMultiOrchestrator.upload( caption, if (sensitiveContent) "" else null, - MediaCompressor.intToCompressorQuality(mediaQualitySlider), + CompressorQuality.fromSlider(mediaQualitySlider), serverToUse, myAccount, - context, + uploader, useH265Codec, stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 3142596c5c..24e986c84b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -46,7 +46,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -72,7 +71,7 @@ import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_description import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer @@ -101,7 +100,6 @@ fun NewMediaView( initialCaption: String = "", ) { val account = accountViewModel.account - val context = LocalContext.current val scrollState = rememberScrollState() @@ -130,7 +128,7 @@ fun NewMediaView( onClose() }, onPost = { - postViewModel.upload(context, accountViewModel, onClose, accountViewModel.toastManager::toast) + postViewModel.upload(accountViewModel.host.mediaUploader, accountViewModel, onClose, accountViewModel.toastManager::toast) postViewModel.selectedServer?.let { account.settings.changeDefaultFileServer(it) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index 54fade5061..31c146955c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -33,11 +33,11 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt index 58f1acc707..425c25f84e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt @@ -39,7 +39,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.upload_file -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.commons.ui.stringRes import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt index a7da527781..465afd5ee8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SharedMediaResolver.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.actions.uploads import android.content.Context import androidx.core.net.toUri -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt index ddab643263..f72973fde9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt @@ -78,18 +78,20 @@ import com.vitorpamplona.amethyst.commons.resources.uploading_state_hashing import com.vitorpamplona.amethyst.commons.resources.uploading_state_ready import com.vitorpamplona.amethyst.commons.resources.uploading_state_server_processing import com.vitorpamplona.amethyst.commons.resources.uploading_state_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.components.AutoNonlazyGrid import com.vitorpamplona.amethyst.commons.ui.note.CloseIcon import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size40Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.playback.composable.VideoView -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt index 151d93b0b3..ecade0bf8c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/TakePicture.kt @@ -52,7 +52,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.no_camera_app_found import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.take_a_picture -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.stringRes import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt index 279d636de8..93a48052a0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt @@ -49,10 +49,11 @@ import com.vitorpamplona.amethyst.commons.resources.uploading_state_hashing import com.vitorpamplona.amethyst.commons.resources.uploading_state_ready import com.vitorpamplona.amethyst.commons.resources.uploading_state_server_processing import com.vitorpamplona.amethyst.commons.resources.uploading_state_uploading +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource @Composable fun UploadProgressIndicator( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt index 4316aa294a..00bd0ac935 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ThinPaddingTextField.kt @@ -53,7 +53,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri import com.vitorpamplona.amethyst.commons.ui.components.imageContentReceiver import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt index edd9282f5b..d03fc98f99 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt @@ -90,6 +90,8 @@ import com.vitorpamplona.amethyst.commons.resources.strip_metadata_description import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_description import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_label +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.note.CancelIcon @@ -102,8 +104,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.ai.MLKitImageLabelService -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt index 43ff22bf9c..bb649a217a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.note.nip22Comments -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -55,7 +54,12 @@ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.IAudience @@ -67,12 +71,9 @@ import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord import com.vitorpamplona.amethyst.commons.ui.text.setTextAndPlaceCursorAtBeginning +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor @@ -787,11 +788,11 @@ open class CommentPostViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, stripMetadata: Boolean = true, ) { try { - uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context, stripMetadata) + uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, uploader, stripMetadata) } catch (_: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -808,7 +809,7 @@ open class CommentPostViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, stripMetadata: Boolean = true, ) { viewModelScope.launch(Dispatchers.IO) { @@ -820,10 +821,10 @@ open class CommentPostViewModel : myMultiOrchestrator.upload( alt, contentWarningReason, - MediaCompressor.intToCompressorQuality(mediaQuality), + CompressorQuality.fromSlider(mediaQuality), server, account, - context, + uploader, stripMetadata = stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, forcedSigner = if (wantsAnonymousPost) anonymousSigner() else null, @@ -832,30 +833,31 @@ open class CommentPostViewModel : if (results.allGood) { val urls = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { - val nip95 = account.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, contentWarningReason) + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.NIP95Result) { + val nip95 = account.createNip95(uploaded.bytes, headerInfo = uploaded.fileHeader, alt, contentWarningReason) nip95attachments = nip95attachments + nip95 val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) } note?.let { "nostr:" + it.toNEvent() } - } else if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { + } else if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { val iMeta = - IMetaTagBuilder(state.result.url) + IMetaTagBuilder(uploaded.url) .apply { - hash(state.result.fileHeader.hash) - size(state.result.fileHeader.size) - state.result.fileHeader.mimeType + hash(uploaded.fileHeader.hash) + size(uploaded.fileHeader.size) + uploaded.fileHeader.mimeType ?.let { mimeType(it) } - state.result.fileHeader.dim + uploaded.fileHeader.dim ?.let { dims(it) } - state.result.fileHeader.blurHash + uploaded.fileHeader.blurHash ?.let { blurhash(it.blurhash) } - state.result.fileHeader.thumbHash + uploaded.fileHeader.thumbHash ?.let { thumbhash(it.thumbhash) } - state.result.magnet?.let { magnet(it) } - state.result.uploadedHash?.let { originalHash(it) } + uploaded.magnet?.let { magnet(it) } + uploaded.uploadedHash?.let { originalHash(it) } alt?.let { alt(it) } contentWarningReason?.let { sensitiveContent(contentWarningReason) } @@ -863,7 +865,7 @@ open class CommentPostViewModel : iMetaAttachments.replace(iMeta.url, iMeta) - state.result.url + uploaded.url } else { null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index 963b9a7e93..e27d53befe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -78,8 +78,8 @@ import com.vitorpamplona.amethyst.commons.resources.post_anonymously import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -472,7 +472,7 @@ private fun GenericCommentPostBody( accountViewModel.account.settings.defaultFileServer, isUploading = postViewModel.mediaUploadTracker.isUploading, onAdd = { alt, server, sensitiveContent, mediaQuality, _, stripMetadata, _ -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context, stripMetadata) + postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, accountViewModel.host.mediaUploader, stripMetadata) accountViewModel.account.settings.changeDefaultFileServer(server) }, onDelete = postViewModel::deleteMediaToUpload, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsImageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsImageScreen.kt index 74a9cee072..a218c7d509 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsImageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/share/ShareNoteAsImageScreen.kt @@ -90,6 +90,9 @@ import com.vitorpamplona.amethyst.commons.resources.share_as_image import com.vitorpamplona.amethyst.commons.resources.share_as_image_generating import com.vitorpamplona.amethyst.commons.resources.share_as_image_url import com.vitorpamplona.amethyst.commons.resources.share_as_image_watermark +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.components.LoadNote import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer @@ -98,10 +101,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size18Modifier +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -268,7 +269,7 @@ fun ShareNoteAsImageScreen( compressionQuality = CompressorQuality.UNCOMPRESSED, server = server, account = account, - context = context, + uploader = accountViewModel.host.mediaUploader, stripMetadata = false, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModelAndroidActions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModelAndroidActions.kt index 0fcc03aaa3..d4d9d0c2c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModelAndroidActions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModelAndroidActions.kt @@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.failed_to_save_the_video import com.vitorpamplona.amethyst.commons.resources.video_saved_to_the_gallery import com.vitorpamplona.amethyst.commons.service.isVideoOnline +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -65,8 +66,8 @@ suspend fun AccountViewModel.checkVideoIsOnline(videoUrl: String): Boolean = htt suspend fun AccountViewModel.uploadMarmotGroupIcon( uri: Uri, mimeType: String?, - context: Context, -): MarmotGroupIconUpload = MarmotGroupIconUploader(account).upload(uri, mimeType, account.settings.defaultFileServer, context) + uploader: MediaUploader, +): MarmotGroupIconUpload = MarmotGroupIconUploader(account).upload(uri, mimeType, account.settings.defaultFileServer, uploader) fun AccountViewModel.saveMediaToGallery( videoUri: String?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt index 310308d505..3f7a1d7466 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AndroidAccountViewModelHost.kt @@ -28,10 +28,12 @@ import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.commons.service.lnurl.LnurlHttpTransport import com.vitorpamplona.amethyst.commons.service.lnurl.OkHttpLnurlTransport import com.vitorpamplona.amethyst.commons.service.pow.PoWJobFailure +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.tor.MoneyOpRelayRouting import com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModelHost import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.dismissNotificationForEvent +import com.vitorpamplona.amethyst.service.uploads.AndroidMediaUploader import com.vitorpamplona.amethyst.ui.note.payViaIntent import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.toHexKey @@ -70,6 +72,8 @@ class AndroidAccountViewModelHost( override val relayAuthState: StateFlow> get() = modules.authCoordinator.receiver.authStateFlow + override val mediaUploader: MediaUploader by lazy { AndroidMediaUploader(modules.appContext) } + override val savedAccounts: Flow> = LocalPreferences .accountsFlow() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt index 67f73fb063..dd2e9abb71 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt @@ -54,7 +54,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.style.TextAlign @@ -108,7 +107,6 @@ fun NewBadgeDialog( accountViewModel: AccountViewModel, ) { val account = accountViewModel.account - val context = LocalContext.current val scrollState = rememberScrollState() @@ -152,7 +150,7 @@ fun NewBadgeDialog( }, onPost = { postViewModel.upload( - context, + accountViewModel.host.mediaUploader, onSuccess = onClose, onError = accountViewModel.toastManager::toast, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt index b13cef87f8..5bfdc7d5c0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.post -import android.content.Context import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf @@ -36,11 +35,13 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip58Badges.definition.tags.ThumbTag import kotlinx.collections.immutable.ImmutableList @@ -110,12 +111,12 @@ class NewBadgeModel : ViewModel() { name.isNotBlank() fun upload( - context: Context, + uploader: MediaUploader, onSuccess: () -> Unit, onError: (String, String) -> Unit, ) { try { - uploadUnsafe(context, onSuccess, onError) + uploadUnsafe(uploader, onSuccess, onError) } catch (e: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -127,7 +128,7 @@ class NewBadgeModel : ViewModel() { } private fun uploadUnsafe( - context: Context, + uploader: MediaUploader, onSuccess: () -> Unit, onError: (String, String) -> Unit, ) { @@ -142,10 +143,10 @@ class NewBadgeModel : ViewModel() { orch.upload( alt = name, contentWarningReason = null, - mediaQuality = MediaCompressor.intToCompressorQuality(mediaQualitySlider), + mediaQuality = CompressorQuality.fromSlider(mediaQualitySlider), server = serverToUse, account = myAccount, - context = context, + uploader = uploader, useH265 = false, stripMetadata = stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt index bca0a57abe..ca9386f767 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/list/metadata/BookmarkGroupMetadataViewModel.kt @@ -39,11 +39,11 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventScreen.kt index 450b025c40..8a8c97f679 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventScreen.kt @@ -275,7 +275,7 @@ private fun ImageRow( if (uri == null) return@rememberLauncherForActivityResult val mime = context.contentResolver.getType(uri) scope.launch { - val ok = vm.uploadAndSetImage(uri, mime, context) + val ok = vm.uploadAndSetImage(uri, mime, accountViewModel.host.mediaUploader) if (!ok) { accountViewModel.toastManager.toast( Res.string.calendar_event_image_upload_failed, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventViewModel.kt index b8f4d1fe12..0467a042a1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/calendars/create/NewCalendarEventViewModel.kt @@ -20,17 +20,17 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.create -import android.content.Context import android.net.Uri import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.nip52Calendar.parseIsoDateToUnixSeconds +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip01Core.tags.people.PTag @@ -157,7 +157,7 @@ class NewCalendarEventViewModel : ViewModel() { suspend fun uploadAndSetImage( uri: Uri, mimeType: String?, - context: Context, + uploader: MediaUploader, ): Boolean { if (!::account.isInitialized) return false isUploadingImage.value = true @@ -172,7 +172,7 @@ class NewCalendarEventViewModel : ViewModel() { compressionQuality = CompressorQuality.MEDIUM, server = server, account = account, - context = context, + uploader = uploader, ) val serverResult = (result as? UploadingState.Finished)?.result as? UploadOrchestrator.OrchestratorResult.ServerResult diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt index 19114bc47b..d6cd50aaa4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt @@ -103,7 +103,9 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_voice_record import com.vitorpamplona.amethyst.commons.resources.cordn_voice_stop import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.loadStringRes @@ -117,7 +119,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.model.cordn.CordnMediaService -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceMessageRecorder @@ -141,6 +142,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.File /** * One cordn room. @@ -518,7 +520,7 @@ private fun CordnGroupChat( attaching = true attachError = null try { - sendAttachment(context, accountViewModel, room, uploadState) + sendAttachment(context, accountViewModel.host.mediaUploader, accountViewModel, room, uploadState) // Only on success: a failed upload leaves the dialog up // with what you picked still in it, so retrying is one // tap rather than the picker again. @@ -1006,6 +1008,7 @@ private class CordnAttachmentException( */ private suspend fun sendAttachment( context: Context, + uploader: MediaUploader, accountViewModel: AccountViewModel, room: CordnGroupChatroom, state: ChatFileUploadState, @@ -1048,8 +1051,8 @@ private suspend fun sendAttachment( item.orchestrator.compressIfNeeded( uri = uri, mimeType = declaredMime, - compressionQuality = MediaCompressor.intToCompressorQuality(state.mediaQualitySlider), - context = context, + compressionQuality = CompressorQuality.fromSlider(state.mediaQualitySlider), + uploader = uploader, ) } val mime = compressed.contentType ?: declaredMime @@ -1092,8 +1095,8 @@ private suspend fun sendAttachment( // copies of a message that is end-to-end encrypted everywhere else — the // same reason the voice path deletes its recording. Both calls no-op on the // user's own file. - item.orchestrator.deleteTempUri(finalUri, uri) - item.orchestrator.deleteTempUri(compressed.uri, uri) + deleteTempFile(finalUri, uri) + deleteTempFile(compressed.uri, uri) } } finally { // Always, not just on success: a failure leaves the dialog up to retry from, @@ -1331,3 +1334,13 @@ private suspend fun sendVoiceNote( withContext(Dispatchers.IO) { recording.file.delete() } } } + +/** Deletes a pipeline temp file; a no-op on the user's own file, which is [originalUri]. */ +private fun deleteTempFile( + tempUri: Uri, + originalUri: Uri, +) { + if (tempUri == originalUri) return + val path = tempUri.path ?: return + if (!File(path).delete()) Log.w("CordnGroupChat") { "Could not delete temp file $path" } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt index 964a47bc57..6b7216fa01 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/CreateGroupScreen.kt @@ -59,7 +59,7 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_keypackage_relays_not import com.vitorpamplona.amethyst.commons.resources.marmot_keypackage_relays_not_set_title import com.vitorpamplona.amethyst.commons.resources.marmot_skip_for_now import com.vitorpamplona.amethyst.commons.resources.marmot_use_outbox_relays -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -122,7 +122,7 @@ fun CreateGroupScreen( val iconChange = pickedIcon?.let { media -> MarmotGroupIconChange.Set( - accountViewModel.uploadMarmotGroupIcon(media.uri, media.mimeType, context), + accountViewModel.uploadMarmotGroupIcon(media.uri, media.mimeType, accountViewModel.host.mediaUploader), ) } ?: MarmotGroupIconChange.Keep // Always commit an initial metadata extension so that diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt index 27df1823cd..40e271da12 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/EditGroupInfoScreen.kt @@ -57,7 +57,7 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_group_name import com.vitorpamplona.amethyst.commons.resources.marmot_group_name_placeholder import com.vitorpamplona.amethyst.commons.resources.marmot_legacy_group_no_avatar_url import com.vitorpamplona.amethyst.commons.resources.save -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -114,7 +114,7 @@ fun EditGroupInfoScreen( val iconChange = pickedIcon?.let { media -> MarmotGroupIconChange.Set( - accountViewModel.uploadMarmotGroupIcon(media.uri, media.mimeType, context), + accountViewModel.uploadMarmotGroupIcon(media.uri, media.mimeType, accountViewModel.host.mediaUploader), ) } ?: if (removeIcon) MarmotGroupIconChange.Clear else MarmotGroupIconChange.Keep accountViewModel.updateMarmotGroupMetadata( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt index f2d5fbc593..b902700071 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupChatView.kt @@ -67,8 +67,8 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_out_of_sync_confirm_b import com.vitorpamplona.amethyst.commons.resources.marmot_out_of_sync_confirm_title import com.vitorpamplona.amethyst.commons.resources.marmot_out_of_sync_reset import com.vitorpamplona.amethyst.commons.resources.reply_here +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -389,6 +389,7 @@ private fun MarmotGroupFileUploadDialog( } }, context = context, + uploader = accountViewModel.host.mediaUploader, onceUploaded = { uploads -> MarmotFileSender(nostrGroupId, accountViewModel).send(uploads) onUpload() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt index 9aba7e30a3..bb320241ca 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/MarmotGroupIconEditor.kt @@ -44,7 +44,7 @@ import com.vitorpamplona.amethyst.commons.resources.marmot_add_photo import com.vitorpamplona.amethyst.commons.resources.marmot_change_photo import com.vitorpamplona.amethyst.commons.resources.marmot_group_icon import com.vitorpamplona.amethyst.commons.resources.marmot_remove_photo -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt index 3ac1b82a62..004bfa0a64 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotFileUploader.kt @@ -25,10 +25,12 @@ import android.net.Uri import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState import com.vitorpamplona.quartz.marmot.appComponents.EncryptedMediaPolicyV2 import com.vitorpamplona.quartz.marmot.appComponents.EncryptedMediaReferenceV2 @@ -78,6 +80,7 @@ class MarmotFileUploader( exporterSecret: ByteArray, onError: (title: String, message: String) -> Unit, context: Context, + uploader: MediaUploader, /** * Produce `encrypted-media-v2` references instead of MIP-04 ones. * Decided by the group's policy component, not by the uploader. @@ -88,7 +91,7 @@ class MarmotFileUploader( viewState.mediaUploadTracker.startUpload(multiOrchestrator.hasNonMedia()) val results = mutableListOf() - val quality = MediaCompressor.intToCompressorQuality(viewState.mediaQualitySlider) + val quality = CompressorQuality.fromSlider(viewState.mediaQualitySlider) val count = multiOrchestrator.size() for (i in 0 until count) { @@ -130,13 +133,13 @@ class MarmotFileUploader( encrypt = cipher, server = viewState.selectedServer, account = account, - context = context, + uploader = uploader, stripMetadata = viewState.stripMetadata, ) val state = item.orchestrator.progressState.value - if (state is UploadingState.Finished && state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - val serverResult = state.result + val serverResult = (state as? UploadingState.Finished)?.result as? UploadOrchestrator.OrchestratorResult.ServerResult + if (serverResult != null) { // The reference is built from what the cipher recorded while // encrypting the bytes the pipeline actually uploaded — after // compression and metadata stripping — because that is what the diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotGroupIconUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotGroupIconUploader.kt index c96b1a3cf2..39004e420c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotGroupIconUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotGroupIconUploader.kt @@ -20,16 +20,16 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send -import android.content.Context import android.net.Uri import com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconUpload import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupImageCipher import com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupImageEncryption import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair @@ -53,13 +53,13 @@ class MarmotGroupIconUploader( uri: Uri, mimeType: String?, server: ServerName, - context: Context, + uploader: MediaUploader, ): MarmotGroupIconUpload { // Compress/downscale up front — avatars don't need full resolution, and a smaller // blob is cheaper for every member to fetch. The MIP-01 crypto uses no AAD and does // not bind the MIME type, so the (possibly transcoded) output type is irrelevant to // decryption; we just hand the compressed bytes to the encrypting uploader. - val compressed = MediaCompressor().compress(uri, mimeType, CompressorQuality.MEDIUM, context.applicationContext) + val compressed = UploadOrchestrator().compressIfNeeded(uri, mimeType, CompressorQuality.MEDIUM, uploader) val uploadMime = compressed.contentType ?: mimeType ?: DEFAULT_MIME val cipher = MarmotGroupImageCipher.forNewImage() val uploadKeySeed = MarmotGroupImageEncryption.generateUploadKey() @@ -76,13 +76,13 @@ class MarmotGroupIconUploader( encrypt = cipher, server = server, account = account, - context = context, + uploader = uploader, stripMetadata = true, forcedSigner = uploadSigner, ) - if (state is UploadingState.Finished && state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - val serverResult = state.result + val serverResult = (state as? UploadingState.Finished)?.result as? UploadOrchestrator.OrchestratorResult.ServerResult + if (serverResult != null) { val hash = serverResult.uploadedHash ?: throw IllegalStateException("Blossom server did not return a content hash for the group icon") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt index feac05bcb6..edc31b853a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/marmotGroup/send/MarmotNewMessageViewModel.kt @@ -33,7 +33,7 @@ import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.latestMarmotEdit import com.vitorpamplona.amethyst.commons.model.marmotGroups.MarmotGroupChatroom -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.onUiThread diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt index 21b6cfc90e..f54529cd63 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/minichat/MinichatScreen.kt @@ -67,8 +67,8 @@ import com.vitorpamplona.amethyst.commons.resources.chat_minichat_title import com.vitorpamplona.amethyst.commons.resources.chat_send_image_title import com.vitorpamplona.amethyst.commons.resources.concord_open_channel import com.vitorpamplona.amethyst.commons.resources.reply_here +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription @@ -240,7 +240,7 @@ fun MinichatScreen( onEncryptedUploadError = { title, message -> launch(Dispatchers.Main) { Toast.makeText(context, "$title: $message", Toast.LENGTH_LONG).show() } }, - context = context, + uploader = accountViewModel.host.mediaUploader, onceUploaded = { uploads -> val caption = uploadState.caption val imetas = if (isConcord) uploads.mapNotNull { it.toConcordImeta() } else uploads.toPlainImetas() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt index 19ff582753..783f35dacd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -48,7 +47,8 @@ import com.vitorpamplona.amethyst.commons.model.location.LocationResult import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestionState import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.IZapField @@ -473,7 +473,7 @@ class ChatNewMessageViewModel : fun uploadAndHold( onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onceUploaded: () -> Unit, ) { val uploadState = uploadState ?: return @@ -487,7 +487,7 @@ class ChatNewMessageViewModel : encryptedUploadErrorMessage = message pendingRetryMode = RetryMode.HOLD }, - context, + uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) { uploadsWaitingToBeSent += it @@ -499,7 +499,7 @@ class ChatNewMessageViewModel : fun uploadAndSend( onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onceUploaded: () -> Unit, ) { val room = room.value ?: return @@ -514,10 +514,10 @@ class ChatNewMessageViewModel : encryptedUploadErrorMessage = message pendingRetryMode = RetryMode.SEND pendingRetryOnError = onError - pendingRetryContext = context + pendingRetryUploader = uploader pendingRetryOnceUploaded = onceUploaded }, - context, + uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) { ChatFileSender(room, account).sendNIP17(it) @@ -532,7 +532,7 @@ class ChatNewMessageViewModel : var encryptedUploadErrorMessage by mutableStateOf(null) var pendingRetryMode by mutableStateOf(null) var pendingRetryOnError by mutableStateOf<((String, String) -> Unit)?>(null) - var pendingRetryContext by mutableStateOf(null) + var pendingRetryUploader by mutableStateOf(null) var pendingRetryOnceUploaded by mutableStateOf<(() -> Unit)?>(null) enum class RetryMode { HOLD, SEND } @@ -542,21 +542,21 @@ class ChatNewMessageViewModel : encryptedUploadErrorMessage = null pendingRetryMode = null pendingRetryOnError = null - pendingRetryContext = null + pendingRetryUploader = null pendingRetryOnceUploaded = null } fun retryWithoutEncryption() { val mode = pendingRetryMode ?: return val onError = pendingRetryOnError - val context = pendingRetryContext + val uploader = pendingRetryUploader val onceUploaded = pendingRetryOnceUploaded val room = room.value val uploadState = uploadState dismissEncryptedUploadError() - if (room == null || uploadState == null || context == null) return + if (room == null || uploadState == null || uploader == null) return uploadState.encryptFiles = false @@ -566,7 +566,7 @@ class ChatNewMessageViewModel : ChatFileUploader(account).justUploadNIP17Unencrypted( uploadState, onError ?: accountViewModel.toastManager::toast, - context, + uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) { uploadsWaitingToBeSent += it @@ -579,7 +579,7 @@ class ChatNewMessageViewModel : ChatFileUploader(account).justUploadNIP17Unencrypted( uploadState, onError ?: accountViewModel.toastManager::toast, - context, + uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) { ChatFileSender(room, account).sendNIP17(it) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/IMetaAttachments.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/IMetaAttachments.kt index 8497a8b240..e04b6ce484 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/IMetaAttachments.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/IMetaAttachments.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.vitorpamplona.amethyst.commons.service.upload.FileHeader -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.service.uploads.prepare import com.vitorpamplona.quartz.nip92IMeta.IMetaTag import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index e54a13f17e..3622a98c27 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -87,8 +87,8 @@ import com.vitorpamplona.amethyst.commons.richtext.EncryptedMediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -308,7 +308,7 @@ fun GroupDMScreenContent( onAdd = { _, server, _, _, _, _, _ -> postViewModel.uploadAndHold( accountViewModel.toastManager::toast, - context, + accountViewModel.host.mediaUploader, onceUploaded = { }, ) accountViewModel.account.settings.changeDefaultFileServer(server) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt index 3ca6a4270d..b8f96867f9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt @@ -42,7 +42,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.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -62,8 +61,8 @@ import com.vitorpamplona.amethyst.commons.resources.retry_without_encryption import com.vitorpamplona.amethyst.commons.resources.this_message_will_disappear_in import com.vitorpamplona.amethyst.commons.resources.upload_without_encryption_warning import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -187,7 +186,6 @@ fun PrivateMessageEditFieldRow( if (channelScreenModel.wantsExpirationDate) { Row(Modifier.fillMaxWidth().padding(vertical = 2.dp), horizontalArrangement = Arrangement.Center) { - val context = LocalContext.current Text( stringRes( Res.string.this_message_will_disappear_in, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileSender.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileSender.kt index c34f038e26..ee873c8af1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileSender.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileSender.kt @@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.uploa import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.quartz.nip01Core.tags.references.references import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt index 14cebf331d..97bacf2bca 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploadDialog.kt @@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -49,7 +48,6 @@ fun RoomChatFileUploadDialog( ) { val roomState = channelScreenModel.room.collectAsStateWithLifecycle() val room = roomState.value ?: return - val context = LocalContext.current ChatFileUploadDialog( state, @@ -67,7 +65,7 @@ fun RoomChatFileUploadDialog( upload = { channelScreenModel.uploadAndSend( onError = accountViewModel.toastManager::toast, - context = context, + uploader = accountViewModel.host.mediaUploader, onceUploaded = onUpload, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploader.kt index 98eaf8e91d..65926bac51 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/ChatFileUploader.kt @@ -20,16 +20,17 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload -import android.content.Context import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_encrypted_media_message import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_encrypted_media_title import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState import com.vitorpamplona.quartz.utils.ciphers.AESGCM @@ -44,7 +45,7 @@ class ChatFileUploader( viewState: ChatFileUploadState, onError: (title: String, message: String) -> Unit, onEncryptedUploadError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onStrippingFailed: suspend () -> Boolean = { true }, onceUploaded: suspend (List) -> Unit, ) { @@ -58,11 +59,11 @@ class ChatFileUploader( orchestrator.uploadEncrypted( viewState.caption, viewState.contentWarningReason, - MediaCompressor.intToCompressorQuality(viewState.mediaQualitySlider), + CompressorQuality.fromSlider(viewState.mediaQualitySlider), cipher, viewState.selectedServer, account, - context, + uploader, stripMetadata = viewState.stripMetadata, onStrippingFailed = onStrippingFailed, ) @@ -70,8 +71,9 @@ class ChatFileUploader( if (results.allGood) { val list = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - SuccessfulUploads(state.result, viewState.caption, viewState.contentWarningReason, cipher) + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + SuccessfulUploads(uploaded, viewState.caption, viewState.contentWarningReason, cipher) } else { null } @@ -97,7 +99,7 @@ class ChatFileUploader( } } } else { - justUploadNIP17Unencrypted(viewState, onError, context, onStrippingFailed, onceUploaded) + justUploadNIP17Unencrypted(viewState, onError, uploader, onStrippingFailed, onceUploaded) } viewState.mediaUploadTracker.finishUpload() @@ -106,7 +108,7 @@ class ChatFileUploader( suspend fun justUploadNIP17Unencrypted( viewState: ChatFileUploadState, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onStrippingFailed: suspend () -> Boolean = { true }, onceUploaded: suspend (List) -> Unit, ) { @@ -117,10 +119,10 @@ class ChatFileUploader( orchestrator.upload( viewState.caption, viewState.contentWarningReason, - MediaCompressor.intToCompressorQuality(viewState.mediaQualitySlider), + CompressorQuality.fromSlider(viewState.mediaQualitySlider), viewState.selectedServer, account, - context, + uploader, stripMetadata = viewState.stripMetadata, onStrippingFailed = onStrippingFailed, ) @@ -128,8 +130,9 @@ class ChatFileUploader( if (results.allGood) { val list = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - SuccessfulUploads(state.result, viewState.caption, viewState.contentWarningReason, null) + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + SuccessfulUploads(uploaded, viewState.caption, viewState.contentWarningReason, null) } else { null } @@ -153,7 +156,7 @@ class ChatFileUploader( suspend fun justUploadNIP04( viewState: ChatFileUploadState, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onStrippingFailed: suspend () -> Boolean = { true }, onceUploaded: suspend (List) -> Unit, ) { @@ -164,10 +167,10 @@ class ChatFileUploader( orchestrator.upload( viewState.caption, viewState.contentWarningReason, - MediaCompressor.intToCompressorQuality(viewState.mediaQualitySlider), + CompressorQuality.fromSlider(viewState.mediaQualitySlider), viewState.selectedServer, account, - context, + uploader, stripMetadata = viewState.stripMetadata, onStrippingFailed = onStrippingFailed, ) @@ -175,8 +178,9 @@ class ChatFileUploader( if (results.allGood) { val list = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - SuccessfulUploads(state.result, viewState.caption, viewState.contentWarningReason, null) + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + SuccessfulUploads(uploaded, viewState.caption, viewState.contentWarningReason, null) } else { null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/SuccessfulUploads.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/SuccessfulUploads.kt index c88223ab0f..f7693c91c6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/SuccessfulUploads.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/upload/SuccessfulUploads.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.upload import androidx.compose.runtime.Stable -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.quartz.utils.ciphers.AESGCM @Stable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt index 976230c024..2964ce1923 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordChannelScreen.kt @@ -73,8 +73,8 @@ import com.vitorpamplona.amethyst.commons.resources.concord_typing_many import com.vitorpamplona.amethyst.commons.resources.concord_typing_one import com.vitorpamplona.amethyst.commons.resources.concord_typing_two import com.vitorpamplona.amethyst.commons.resources.reply_here +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.feeds.DmHistoryLoadingCard import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachCursor import com.vitorpamplona.amethyst.commons.ui.feeds.RelayReachMarkers @@ -578,7 +578,7 @@ private fun ConcordFileUploadDialog( onEncryptedUploadError = { title, message -> scope.launch(Dispatchers.Main) { Toast.makeText(context, "$title: $message", Toast.LENGTH_LONG).show() } }, - context = context, + uploader = accountViewModel.host.mediaUploader, onceUploaded = { uploads -> val imetas = uploads.mapNotNull { it.toConcordImeta() } if (imetas.isNotEmpty()) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt index 8d9cc8de9e..9c33247bb2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt @@ -26,10 +26,12 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.DEFAULT_MEDIA_SERVERS import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.utils.ciphers.AESGCM @@ -60,6 +62,7 @@ class ConcordImageUploader( suspend fun uploadEncrypted( uri: Uri, context: Context, + uploader: MediaUploader, ): ImagePointer = withContext(Dispatchers.IO) { // Fresh random key + nonce per image; we hold onto them to build the pointer below since the @@ -76,7 +79,7 @@ class ConcordImageUploader( encrypt = cipher, server = resolveBlossomServer(), account = account, - context = context, + uploader = uploader, ) val result = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt index 970e331ae9..7f3ab7b079 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt @@ -168,7 +168,7 @@ private fun ConcordIconHero( uploading = true scope.launch { try { - icon.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(uri, context) + icon.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(uri, context, accountViewModel.host.mediaUploader) } catch (e: CancellationException) { throw e } catch (e: Exception) { @@ -239,7 +239,7 @@ private fun ConcordBannerHero( uploading = true scope.launch { try { - banner.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(uri, context) + banner.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(uri, context, accountViewModel.host.mediaUploader) } catch (e: CancellationException) { throw e } catch (e: Exception) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt index 6113ce792f..aa5cf9c9fb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/send/ConcordNewMessageViewModel.kt @@ -34,7 +34,7 @@ import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestionState -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.onUiThread diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt index 5f1c7228a8..adac712a2b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataViewModel.kt @@ -40,13 +40,13 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt index 0b9b54a6bf..ab1c218a90 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt @@ -43,11 +43,11 @@ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt index 6ae466e4a3..539f57af85 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelFileUploadDialog.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -59,7 +58,6 @@ fun ChannelFileUploadDialog( nav: INav, ) { val channel = channelScreenModel.channel ?: return - val context = LocalContext.current ChatFileUploadDialog( state, @@ -133,7 +131,7 @@ fun ChannelFileUploadDialog( upload = { channelScreenModel.upload( onError = accountViewModel.toastManager::toast, - context = context, + uploader = accountViewModel.host.mediaUploader, onceUploaded = onUpload, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt index 6c81894517..68ae70fdf0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -59,18 +58,20 @@ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.UrlParser import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.ReplyMode import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState @@ -495,11 +496,11 @@ open class ChannelNewMessageViewModel : fun upload( onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onceUploaded: suspend () -> Unit, ) { try { - uploadUnsafe(onError, context, onceUploaded) + uploadUnsafe(onError, uploader, onceUploaded) } catch (_: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -512,7 +513,7 @@ open class ChannelNewMessageViewModel : fun uploadUnsafe( onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onceUploaded: suspend () -> Unit, ) { viewModelScope.launch(Dispatchers.IO) { @@ -526,10 +527,10 @@ open class ChannelNewMessageViewModel : myMultiOrchestrator.upload( uploadState.caption, uploadState.contentWarningReason, - MediaCompressor.intToCompressorQuality(uploadState.mediaQualitySlider), + CompressorQuality.fromSlider(uploadState.mediaQualitySlider), uploadState.selectedServer, account, - context, + uploader, stripMetadata = uploadState.stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) @@ -537,16 +538,17 @@ open class ChannelNewMessageViewModel : if (results.allGood) { val urls = results.successful.mapNotNull { upload -> - if (upload.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { - val nip95 = account.createNip95(upload.result.bytes, headerInfo = upload.result.fileHeader, uploadState.caption, uploadState.contentWarningReason) + val uploaded = upload.result + if (uploaded is UploadOrchestrator.OrchestratorResult.NIP95Result) { + val nip95 = account.createNip95(uploaded.bytes, headerInfo = uploaded.fileHeader, uploadState.caption, uploadState.contentWarningReason) nip95attachments = nip95attachments + nip95 val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) } note?.toNostrUri() - } else if (upload.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - iMetaAttachments.add(upload.result, uploadState.caption, uploadState.contentWarningReason) + } else if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + iMetaAttachments.add(uploaded, uploadState.caption, uploadState.contentWarningReason) - upload.result.url + uploaded.url } else { null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt index ca7c486751..3ad55a0c43 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt @@ -53,8 +53,8 @@ import com.vitorpamplona.amethyst.commons.resources.buzz_editing_banner import com.vitorpamplona.amethyst.commons.resources.cancel import com.vitorpamplona.amethyst.commons.resources.reply_here import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt index cbb708ea7c..2703b316e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadState.kt @@ -28,9 +28,9 @@ import androidx.compose.runtime.setValue import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing import kotlinx.collections.immutable.ImmutableList @Stable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt index 85dc127fa6..0aef57488b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.newCommunity -import android.content.Context import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -39,11 +38,13 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions @@ -271,12 +272,12 @@ class NewCommunityModel : ViewModel() { @OptIn(ExperimentalUuidApi::class) fun publish( - context: Context, + uploader: MediaUploader, onSuccess: () -> Unit, onError: (String, String) -> Unit, ) { try { - publishUnsafe(context, onSuccess, onError) + publishUnsafe(uploader, onSuccess, onError) } catch (e: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -289,7 +290,7 @@ class NewCommunityModel : ViewModel() { @OptIn(ExperimentalUuidApi::class) private fun publishUnsafe( - context: Context, + uploader: MediaUploader, onSuccess: () -> Unit, onError: (String, String) -> Unit, ) { @@ -300,7 +301,7 @@ class NewCommunityModel : ViewModel() { try { val uploadedUrl = if (multiOrchestrator != null) { - uploadImageIfAny(context, myAccount, onError) ?: return@launch + uploadImageIfAny(uploader, myAccount, onError) ?: return@launch } else { null } @@ -371,7 +372,7 @@ class NewCommunityModel : ViewModel() { } private suspend fun uploadImageIfAny( - context: Context, + uploader: MediaUploader, myAccount: Account, onError: (String, String) -> Unit, ): String? { @@ -382,10 +383,10 @@ class NewCommunityModel : ViewModel() { orch.upload( alt = name.trim().ifBlank { "Community cover" }, contentWarningReason = null, - mediaQuality = MediaCompressor.intToCompressorQuality(mediaQualitySlider), + mediaQuality = CompressorQuality.fromSlider(mediaQualitySlider), server = serverToUse, account = myAccount, - context = context, + uploader = uploader, useH265 = false, stripMetadata = stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt index c0d669ab26..3afb77c81c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt @@ -58,7 +58,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign @@ -137,7 +136,6 @@ private fun CommunityFormScreen( nav: INav, ) { val model: NewCommunityModel = viewModel() - val context = LocalContext.current LaunchedEffect(accountViewModel.account) { model.init(accountViewModel.account) @@ -185,7 +183,7 @@ private fun CommunityFormScreen( }, onPost = { model.publish( - context = context, + uploader = accountViewModel.host.mediaUploader, onSuccess = { nav.popBack() }, onError = accountViewModel.toastManager::toast, ) @@ -201,7 +199,7 @@ private fun CommunityFormScreen( }, onPost = { model.publish( - context = context, + uploader = accountViewModel.host.mediaUploader, onSuccess = { nav.popBack() }, onError = accountViewModel.toastManager::toast, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt index c75226bce1..48889ad2d5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt @@ -101,9 +101,9 @@ import com.vitorpamplona.amethyst.commons.resources.url_slug_placeholder import com.vitorpamplona.amethyst.commons.resources.your_article_title import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -238,15 +238,13 @@ private fun MarkdownPostScreenBody( .fillMaxWidth() .verticalScroll(scrollState, reverseScrolling = true), ) { - val context = LocalContext.current - // Banner image area BannerImageArea( coverImageUrl = postViewModel.coverImageUrl, isUploading = postViewModel.isUploadingCoverImage, accountViewModel = accountViewModel, onImageSelected = { - postViewModel.uploadCoverImage(it, context, onError = accountViewModel.toastManager::toast) + postViewModel.uploadCoverImage(it, accountViewModel.host.mediaUploader, onError = accountViewModel.toastManager::toast) }, onClear = { postViewModel.coverImageUrl = "" @@ -498,7 +496,7 @@ private fun MarkdownPostScreenBody( accountViewModel.account.settings.defaultFileServer, isUploading = postViewModel.mediaUploadTracker.isUploading, onAdd = { alt, server, sensitiveContent, mediaQuality, useH265, stripMetadata, convertGifToMp4 -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, uploadContext, useH265, stripMetadata, convertGifToMp4) + postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, accountViewModel.host.mediaUploader, useH265, stripMetadata, convertGifToMp4) accountViewModel.account.settings.changeDefaultFileServer(server) }, onDelete = postViewModel::deleteMediaToUpload, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt index 7856a09ef0..5514129da4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -46,7 +45,6 @@ import com.vitorpamplona.amethyst.commons.model.composer.SplitBuilder import com.vitorpamplona.amethyst.commons.model.composer.toZapSplitSetup import com.vitorpamplona.amethyst.commons.model.location.LocationResult import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName -import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState.EmojiMedia import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiSuggestionState import com.vitorpamplona.amethyst.commons.resources.Res @@ -57,7 +55,13 @@ import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState @@ -67,15 +71,9 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader -import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor @@ -122,7 +120,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch -import kotlin.coroutines.cancellation.CancellationException import kotlin.uuid.ExperimentalUuidApi @Stable @@ -467,7 +464,7 @@ class LongFormPostViewModel : fun uploadCoverImage( uri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, ) { accountViewModel.launchSigner { @@ -475,7 +472,7 @@ class LongFormPostViewModel : try { directUpload( galleryUri = uri, - context = context, + uploader = uploader, onError = onError, )?.let { coverImageUrl = it @@ -488,49 +485,36 @@ class LongFormPostViewModel : private suspend fun directUpload( galleryUri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, ): String? { - val compResult = MediaCompressor().compress(galleryUri.uri, galleryUri.mimeType, CompressorQuality.MEDIUM, context.applicationContext) + val state = + UploadOrchestrator().upload( + uri = galleryUri.uri, + mimeType = galleryUri.mimeType, + alt = null, + contentWarningReason = null, + compressionQuality = CompressorQuality.MEDIUM, + server = account.settings.defaultFileServer, + account = account, + uploader = uploader, + ) - return try { - val result = - if (account.settings.defaultFileServer.type == ServerType.NIP96) { - Nip96Uploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = accountViewModel.httpClientBuilder::okHttpClientForUploads, - onProgress = {}, - httpAuth = account::createHTTPAuthorization, - context = context, - ) + return when (state) { + is UploadingState.Finished -> { + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + uploaded.url } else { - BlossomUploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = accountViewModel.httpClientBuilder::okHttpClientForUploads, - httpAuth = account::createBlossomUploadAuth, - context = context, - ) + onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading)) + null } - - if (result.url == null) { - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading)) } - - result.url - } catch (e: Exception) { - if (e is CancellationException) throw e - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) - null + is UploadingState.Error -> { + onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(state.errorResource, *state.params)) + null + } + else -> null } } @@ -540,13 +524,13 @@ class LongFormPostViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, useH265: Boolean, stripMetadata: Boolean = true, convertGifToMp4: Boolean = false, ) { try { - uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context, useH265, stripMetadata, convertGifToMp4) + uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, uploader, useH265, stripMetadata, convertGifToMp4) } catch (_: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -563,7 +547,7 @@ class LongFormPostViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, useH265: Boolean, stripMetadata: Boolean = true, convertGifToMp4: Boolean = false, @@ -577,10 +561,10 @@ class LongFormPostViewModel : myMultiOrchestrator.upload( alt, contentWarningReason, - MediaCompressor.intToCompressorQuality(mediaQuality), + CompressorQuality.fromSlider(mediaQuality), server, account, - context, + uploader, useH265, stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, @@ -590,29 +574,30 @@ class LongFormPostViewModel : if (results.allGood) { val urls = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { val iMeta = - IMetaTagBuilder(state.result.url) + IMetaTagBuilder(uploaded.url) .apply { - hash(state.result.fileHeader.hash) - size(state.result.fileHeader.size) - state.result.fileHeader.mimeType + hash(uploaded.fileHeader.hash) + size(uploaded.fileHeader.size) + uploaded.fileHeader.mimeType ?.let { mimeType(it) } - state.result.fileHeader.dim + uploaded.fileHeader.dim ?.let { dims(it) } - state.result.fileHeader.blurHash + uploaded.fileHeader.blurHash ?.let { blurhash(it.blurhash) } - state.result.fileHeader.thumbHash + uploaded.fileHeader.thumbHash ?.let { thumbhash(it.thumbhash) } - state.result.magnet?.let { magnet(it) } - state.result.uploadedHash?.let { originalHash(it) } + uploaded.magnet?.let { magnet(it) } + uploaded.uploadedHash?.let { originalHash(it) } alt?.let { alt(it) } contentWarningReason?.let { sensitiveContent(contentWarningReason) } }.build() iMetaAttachments.replace(iMeta.url, iMeta) - val markdownImage = "![${alt ?: ""}](${state.result.url})" + val markdownImage = "![${alt ?: ""}](${uploaded.url})" markdownImage } else { null diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index d8def28c87..8c2f501739 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -52,8 +52,8 @@ import com.vitorpamplona.amethyst.commons.resources.lightning_invoice import com.vitorpamplona.amethyst.commons.resources.new_product import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -292,7 +292,7 @@ private fun NewProductBody( defaultServer = accountViewModel.account.settings.defaultFileServer, isUploading = postViewModel.mediaUploadTracker.isUploading, onAdd = { alt, server, sensitiveContent, mediaQuality, _, stripMetadata, _ -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context, stripMetadata) + postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, accountViewModel.host.mediaUploader, stripMetadata) accountViewModel.account.settings.changeDefaultFileServer(server) }, onDelete = postViewModel::deleteMediaToUpload, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt index 0a19fa5d04..a5b3dfdad5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -52,7 +51,12 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState @@ -61,12 +65,9 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor @@ -400,7 +401,7 @@ open class NewProductViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, stripMetadata: Boolean = true, ) { viewModelScope.launch(Dispatchers.IO) { @@ -413,40 +414,41 @@ open class NewProductViewModel : myMultiOrchestrator.upload( alt, contentWarningReason, - MediaCompressor.intToCompressorQuality(mediaQuality), + CompressorQuality.fromSlider(mediaQuality), server, myAccount, - context, + uploader, stripMetadata = stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) if (results.allGood) { val urls = - results.successful.mapNotNull { - if (it.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - if (it.result.fileHeader.mimeType + results.successful.mapNotNull { finished -> + val uploaded = finished.result + if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + if (uploaded.fileHeader.mimeType ?.startsWith("image") == true ) { productImages = productImages + ProductImageMeta( - url = it.result.url, - mimeType = it.result.fileHeader.mimeType, + url = uploaded.url, + mimeType = uploaded.fileHeader.mimeType, blurhash = - it.result.fileHeader.blurHash + uploaded.fileHeader.blurHash ?.blurhash, - dimension = it.result.fileHeader.dim, + dimension = uploaded.fileHeader.dim, alt = alt, - hash = it.result.fileHeader.hash, - size = it.result.fileHeader.size, + hash = uploaded.fileHeader.hash, + size = uploaded.fileHeader.size, thumbhash = - it.result.fileHeader.thumbHash + uploaded.fileHeader.thumbHash ?.thumbhash, ) } else { - iMetaDescription.add(it.result, alt, contentWarningReason) + iMetaDescription.add(uploaded, alt, contentWarningReason) - it.result.url + uploaded.url } } else { null diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt index 6f6a51f5e4..751bce6682 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt @@ -38,10 +38,10 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt index 8d004ebec4..964abeef53 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt @@ -42,11 +42,11 @@ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt index 6e244bc1e9..d99e892f54 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheScreen.kt @@ -67,7 +67,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -142,7 +141,6 @@ fun NewGeocacheScreen( ) { val model: NewGeocacheViewModel = viewModel() val scope = rememberCoroutineScope() - val context = LocalContext.current remember(editKind, editPubKeyHex, editDTag, prefillGeohash) { model.init(accountViewModel) @@ -158,7 +156,7 @@ fun NewGeocacheScreen( val photoPicker = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> - if (uri != null) scope.launch { model.uploadImage(uri, null, context) } + if (uri != null) scope.launch { model.uploadImage(uri, null, accountViewModel.host.mediaUploader) } } Scaffold( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheViewModel.kt index e1dfe1e218..1c60602098 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/create/NewGeocacheViewModel.kt @@ -20,17 +20,17 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.create -import android.content.Context import android.net.Uri import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.toHexKey @@ -177,7 +177,7 @@ class NewGeocacheViewModel : ViewModel() { suspend fun uploadImage( uri: Uri, mimeType: String?, - context: Context, + uploader: MediaUploader, ): Boolean { if (!::account.isInitialized) return false isUploading.value = true @@ -191,7 +191,7 @@ class NewGeocacheViewModel : ViewModel() { compressionQuality = CompressorQuality.MEDIUM, server = account.settings.defaultFileServer, account = account, - context = context, + uploader = uploader, ) val url = ((result as? UploadingState.Finished)?.result as? UploadOrchestrator.OrchestratorResult.ServerResult)?.url diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt index bbb1bef126..ac8b6974b9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt @@ -63,7 +63,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -124,7 +123,6 @@ fun LogGeocacheFindScreen( val address = remember(kind, pubKeyHex, dTag) { Address(kind, pubKeyHex, dTag) } val model: LogGeocacheFindViewModel = viewModel(key = "LogGeocacheFind-${address.toValue()}") val scope = rememberCoroutineScope() - val context = LocalContext.current remember(address) { model.init(accountViewModel, address) @@ -137,7 +135,7 @@ fun LogGeocacheFindScreen( val photoPicker = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> if (uri != null) { - scope.launch { model.uploadImage(uri, null, context) } + scope.launch { model.uploadImage(uri, null, accountViewModel.host.mediaUploader) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt index 56711ae236..f231ebdc51 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt @@ -20,17 +20,17 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.log -import android.content.Context import android.net.Uri import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair @@ -185,7 +185,7 @@ class LogGeocacheFindViewModel : ViewModel() { suspend fun uploadImage( uri: Uri, mimeType: String?, - context: Context, + uploader: MediaUploader, ): Boolean { if (!::account.isInitialized) return false isUploading.value = true @@ -199,7 +199,7 @@ class LogGeocacheFindViewModel : ViewModel() { compressionQuality = CompressorQuality.MEDIUM, server = account.settings.defaultFileServer, account = account, - context = context, + uploader = uploader, ) val url = ((result as? UploadingState.Finished)?.result as? UploadOrchestrator.OrchestratorResult.ServerResult)?.url diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt index fb41fd271c..c5f9554506 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geohash/GeoHashPostScreen.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.cache.LocalCache -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.Nav import com.vitorpamplona.amethyst.ui.note.nip22Comments.CommentPostViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt index ebc46566e7..f82ffcca8b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/hashtag/HashtagPostScreen.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.cache.LocalCache -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.Nav import com.vitorpamplona.amethyst.ui.note.nip22Comments.CommentPostViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index 8c3f3973e5..b954506508 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -109,8 +109,8 @@ import com.vitorpamplona.amethyst.commons.resources.schedule_post_always_on_prom import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.PostingTopBar @@ -616,7 +616,7 @@ private fun NewPostScreenBody( accountViewModel.account.settings.defaultFileServer, isUploading = postViewModel.mediaUploadTracker.isUploading, onAdd = { alt, server, sensitiveContent, mediaQuality, useH265, stripMetadata, convertGifToMp4 -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context, useH265, stripMetadata, convertGifToMp4) + postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, accountViewModel.host.mediaUploader, useH265, stripMetadata, convertGifToMp4) accountViewModel.account.settings.changeDefaultFileServer(server) }, onDelete = postViewModel::deleteMediaToUpload, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index 09dc1dc05a..b689be1da0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -69,7 +69,13 @@ import com.vitorpamplona.amethyst.commons.service.ai.WritingTone import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.notify.IAudience @@ -81,17 +87,12 @@ import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord import com.vitorpamplona.amethyst.commons.ui.text.setTextAndPlaceCursorAtBeginning +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState import com.vitorpamplona.amethyst.service.ai.WritingAssistantFactory import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceAnonymizationController import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments @@ -1578,13 +1579,13 @@ open class ShortNotePostViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, useH265: Boolean, stripMetadata: Boolean = true, convertGifToMp4: Boolean = false, ) { try { - uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context, useH265, stripMetadata, convertGifToMp4) + uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, uploader, useH265, stripMetadata, convertGifToMp4) } catch (_: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -1601,7 +1602,7 @@ open class ShortNotePostViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, useH265: Boolean, stripMetadata: Boolean = true, convertGifToMp4: Boolean = false, @@ -1615,10 +1616,10 @@ open class ShortNotePostViewModel : myMultiOrchestrator.upload( alt, contentWarningReason, - MediaCompressor.intToCompressorQuality(mediaQuality), + CompressorQuality.fromSlider(mediaQuality), server, account, - context, + uploader, useH265, stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, @@ -1629,30 +1630,31 @@ open class ShortNotePostViewModel : if (results.allGood) { val urls = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { - val nip95 = account.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, contentWarningReason) + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.NIP95Result) { + val nip95 = account.createNip95(uploaded.bytes, headerInfo = uploaded.fileHeader, alt, contentWarningReason) nip95attachments = nip95attachments + nip95 val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) } note?.let { "nostr:" + it.toNEvent() } - } else if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { + } else if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { val iMeta = - IMetaTagBuilder(state.result.url) + IMetaTagBuilder(uploaded.url) .apply { - hash(state.result.fileHeader.hash) - size(state.result.fileHeader.size) - state.result.fileHeader.mimeType + hash(uploaded.fileHeader.hash) + size(uploaded.fileHeader.size) + uploaded.fileHeader.mimeType ?.let { mimeType(it) } - state.result.fileHeader.dim + uploaded.fileHeader.dim ?.let { dims(it) } - state.result.fileHeader.blurHash + uploaded.fileHeader.blurHash ?.let { blurhash(it.blurhash) } - state.result.fileHeader.thumbHash + uploaded.fileHeader.thumbHash ?.let { thumbhash(it.thumbhash) } - state.result.magnet?.let { magnet(it) } - state.result.uploadedHash?.let { originalHash(it) } + uploaded.magnet?.let { magnet(it) } + uploaded.uploadedHash?.let { originalHash(it) } alt?.let { alt(it) } contentWarningReason?.let { sensitiveContent(contentWarningReason) } @@ -1660,7 +1662,7 @@ open class ShortNotePostViewModel : iMetaAttachments.replace(iMeta.url, iMeta) - state.result.url + uploaded.url } else { null } @@ -1956,7 +1958,7 @@ open class ShortNotePostViewModel : compressionQuality = CompressorQuality.UNCOMPRESSED, server = server, account = account, - context = appContext, + uploader = accountViewModel.host.mediaUploader, useH265 = false, forcedSigner = if (wantsAnonymousPost) anonymousSigner() else null, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyViewModel.kt index 0dfff06e30..850b450647 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/VoiceReplyViewModel.kt @@ -38,12 +38,12 @@ import com.vitorpamplona.amethyst.commons.resources.upload_error_voice_message_e import com.vitorpamplona.amethyst.commons.resources.upload_error_voice_message_failed import com.vitorpamplona.amethyst.commons.resources.upload_error_voice_message_nip95_not_supported import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.util.deleteOrWarn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult import com.vitorpamplona.amethyst.ui.actions.uploads.VoiceAnonymizationController import com.vitorpamplona.quartz.nip01Core.tags.people.toPTag @@ -204,7 +204,7 @@ class VoiceReplyViewModel : ViewModel() { compressionQuality = CompressorQuality.UNCOMPRESSED, server = serverToUse, account = accountViewModel.account, - context = Amethyst.instance.appContext, + uploader = accountViewModel.host.mediaUploader, useH265 = false, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt index 5f38ec2d7b..5b66e7921c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/FollowPackMetadataViewModel.kt @@ -39,11 +39,11 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt index 721bd81a87..d3d833908c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/lists/list/metadata/PeopleListMetadataViewModel.kt @@ -39,11 +39,11 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt index f5e4fadc27..72ac47929c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/longs/NewLongVideoButton.kt @@ -50,8 +50,8 @@ import com.vitorpamplona.amethyst.commons.resources.ic_compose import com.vitorpamplona.amethyst.commons.resources.new_long_video import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/MusicComposerUploadUi.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/MusicComposerUploadUi.kt index 7894e15e7e..c535b03b39 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/MusicComposerUploadUi.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/MusicComposerUploadUi.kt @@ -50,9 +50,9 @@ import coil3.compose.rememberAsyncImagePainter import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import org.jetbrains.compose.resources.StringResource diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt index 664693d762..2c1d87d16d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt @@ -27,14 +27,15 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState import com.vitorpamplona.quartz.experimental.music.playlist.MusicPlaylistEvent import com.vitorpamplona.quartz.nip01Core.core.Address import kotlinx.collections.immutable.ImmutableList @@ -214,10 +215,11 @@ class NewMusicPlaylistViewModel : ViewModel() { coverOrchestrator = coverMedia.value, existingCoverUrl = coverUrl.value.trim().ifBlank { null }, server = server, - quality = MediaCompressor.intToCompressorQuality(mediaQualitySlider.value), + quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, loadedEvent = loadedEvent, appContext = context.applicationContext, + uploader = accountViewModel.host.mediaUploader, ) isSending.value = true @@ -265,6 +267,7 @@ class NewMusicPlaylistViewModel : ViewModel() { val stripMetadata: Boolean, val loadedEvent: MusicPlaylistEvent?, val appContext: Context, + val uploader: MediaUploader, ) private suspend fun runCoverUpload( @@ -279,13 +282,13 @@ class NewMusicPlaylistViewModel : ViewModel() { mediaQuality = snapshot.quality, server = server, account = account, - context = snapshot.appContext, + uploader = snapshot.uploader, useH265 = false, stripMetadata = snapshot.stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) if (!res.allGood) { - throw UploadException(formatUploadErrors(res.errors, snapshot.appContext)) + throw UploadException(formatUploadErrors(res.errors)) } return firstUploadedUrl(res.successful) ?: throw UploadException("Server didn't return a URL for the uploaded cover.") @@ -300,10 +303,7 @@ class NewMusicPlaylistViewModel : ViewModel() { .firstNotNullOfOrNull { it.result as? UploadOrchestrator.OrchestratorResult.ServerResult } ?.url - private suspend fun formatUploadErrors( - errors: List, - context: Context, - ): String = + private suspend fun formatUploadErrors(errors: List): String = errors .map { loadStringRes(it.errorResource, *it.params) } .distinct() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt index 900b47f545..a8b4306067 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt @@ -84,8 +84,8 @@ import com.vitorpamplona.amethyst.commons.resources.music_track_title_placeholde import com.vitorpamplona.amethyst.commons.resources.music_track_uploading_banner import com.vitorpamplona.amethyst.commons.resources.new_music_track import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt index 281cbf3855..e4b010cf45 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt @@ -30,13 +30,15 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.quartz.experimental.music.track.MusicTrackEvent import com.vitorpamplona.quartz.nip01Core.core.Address import kotlinx.collections.immutable.ImmutableList @@ -288,10 +290,11 @@ class NewMusicTrackViewModel : ViewModel() { existingCoverUrl = coverUrl.value.trim().ifBlank { null }, existingAudioUrl = audioUrl.value.trim(), server = server, - quality = MediaCompressor.intToCompressorQuality(mediaQualitySlider.value), + quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, loadedEvent = loadedEvent, appContext = context.applicationContext, + uploader = accountViewModel.host.mediaUploader, ) isSending.value = true @@ -363,6 +366,7 @@ class NewMusicTrackViewModel : ViewModel() { val stripMetadata: Boolean, val loadedEvent: MusicTrackEvent?, val appContext: Context, + val uploader: MediaUploader, ) /** @@ -403,13 +407,13 @@ class NewMusicTrackViewModel : ViewModel() { mediaQuality = snapshot.quality, server = snapshot.server, account = account, - context = snapshot.appContext, + uploader = snapshot.uploader, useH265 = false, stripMetadata = snapshot.stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) if (!res.allGood) { - throw UploadException(kind, formatUploadErrors(res.errors, snapshot.appContext)) + throw UploadException(kind, formatUploadErrors(res.errors)) } return firstUploadedUrl(res.successful) ?: throw UploadException(kind, "Server didn't return a URL for the uploaded $kind.") @@ -420,15 +424,12 @@ class NewMusicTrackViewModel : ViewModel() { details: String, ) : Exception("$kind upload failed: $details") - private fun firstUploadedUrl(successful: List): String? = + private fun firstUploadedUrl(successful: List): String? = successful .firstNotNullOfOrNull { it.result as? UploadOrchestrator.OrchestratorResult.ServerResult } ?.url - private suspend fun formatUploadErrors( - errors: List, - context: Context, - ): String = + private suspend fun formatUploadErrors(errors: List): String = errors .map { loadStringRes(it.errorResource, *it.params) } .distinct() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt index 09bd48895a..e72eba5d8d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/create/CreateNestViewModel.kt @@ -31,11 +31,11 @@ import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_de import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MetadataStripper import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt index 2f33c77186..ac621ee4df 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestEditFieldRow.kt @@ -40,8 +40,8 @@ import com.vitorpamplona.amethyst.commons.nip30CustomEmojis.ui.ShowEmojiSuggesti import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.reply_here import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowUserSuggestionList import com.vitorpamplona.amethyst.commons.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestFileUploadDialog.kt index 8c8c0e6afc..111e99080d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestFileUploadDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestFileUploadDialog.kt @@ -29,7 +29,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable 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 com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -56,7 +55,6 @@ fun NestFileUploadDialog( nav: INav, ) { val room = nestScreenModel.room ?: return - val context = LocalContext.current val host = room.author ?: return val title = (room.event as? MeetingSpaceEvent)?.room().orEmpty() @@ -93,7 +91,7 @@ fun NestFileUploadDialog( upload = { nestScreenModel.upload( onError = accountViewModel.toastManager::toast, - context = context, + uploader = accountViewModel.host.mediaUploader, onceUploaded = onUpload, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt index 6201c18504..378d7a8aa3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/chat/NestNewMessageViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.chat -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -50,17 +49,19 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.richtext.UrlParser import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.utils.ChatFileUploadState @@ -349,11 +350,11 @@ open class NestNewMessageViewModel : fun upload( onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onceUploaded: suspend () -> Unit, ) { try { - uploadUnsafe(onError, context, onceUploaded) + uploadUnsafe(onError, uploader, onceUploaded) } catch (_: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -366,7 +367,7 @@ open class NestNewMessageViewModel : fun uploadUnsafe( onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, onceUploaded: suspend () -> Unit, ) { viewModelScope.launch(Dispatchers.IO) { @@ -380,10 +381,10 @@ open class NestNewMessageViewModel : myMultiOrchestrator.upload( uploadState.caption, uploadState.contentWarningReason, - MediaCompressor.intToCompressorQuality(uploadState.mediaQualitySlider), + CompressorQuality.fromSlider(uploadState.mediaQualitySlider), uploadState.selectedServer, account, - context, + uploader, stripMetadata = uploadState.stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) @@ -391,16 +392,17 @@ open class NestNewMessageViewModel : if (results.allGood) { val urls = results.successful.mapNotNull { upload -> - if (upload.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { - val nip95 = account.createNip95(upload.result.bytes, headerInfo = upload.result.fileHeader, uploadState.caption, uploadState.contentWarningReason) + val uploaded = upload.result + if (uploaded is UploadOrchestrator.OrchestratorResult.NIP95Result) { + val nip95 = account.createNip95(uploaded.bytes, headerInfo = uploaded.fileHeader, uploadState.caption, uploadState.contentWarningReason) nip95attachments = nip95attachments + nip95 val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) } note?.toNostrUri() - } else if (upload.result is UploadOrchestrator.OrchestratorResult.ServerResult) { - iMetaAttachments.add(upload.result, uploadState.caption, uploadState.contentWarningReason) + } else if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + iMetaAttachments.add(uploaded, uploadState.caption, uploadState.contentWarningReason) - upload.result.url + uploaded.url } else { null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index cc158ece0c..248c8cb6a7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -47,7 +47,6 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -60,8 +59,8 @@ import com.vitorpamplona.amethyst.commons.resources.messages_new_message_to_capt import com.vitorpamplona.amethyst.commons.resources.public_message import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -260,13 +259,12 @@ fun PublicMessageScreenContent( verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), ) { - val context = LocalContext.current ImageVideoDescription( it, accountViewModel.account.settings.defaultFileServer, isUploading = postViewModel.mediaUploadTracker.isUploading, onAdd = { alt, server, sensitiveContent, mediaQuality, _, stripMetadata, _ -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context, stripMetadata) + postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, accountViewModel.host.mediaUploader, stripMetadata) accountViewModel.account.settings.changeDefaultFileServer(server) }, onDelete = postViewModel::deleteMediaToUpload, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt index bfdf88f3bf..4031174597 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages -import android.content.Context import androidx.compose.foundation.text.input.TextFieldState import androidx.compose.foundation.text.input.clearText import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd @@ -54,7 +53,12 @@ import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_ import com.vitorpamplona.amethyst.commons.resources.read_only_user import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState @@ -63,12 +67,9 @@ import com.vitorpamplona.amethyst.commons.ui.text.currentWord import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaAttachments import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.UserSuggestionAnchor @@ -430,11 +431,11 @@ class NewPublicMessageViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, stripMetadata: Boolean = true, ) { try { - uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context, stripMetadata) + uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, uploader, stripMetadata) } catch (e: SignerExceptions.ReadOnlyException) { viewModelScope.launch { onError( @@ -451,7 +452,7 @@ class NewPublicMessageViewModel : mediaQuality: Int, server: ServerName, onError: (title: String, message: String) -> Unit, - context: Context, + uploader: MediaUploader, stripMetadata: Boolean = true, ) { viewModelScope.launch(Dispatchers.IO) { @@ -463,10 +464,10 @@ class NewPublicMessageViewModel : myMultiOrchestrator.upload( alt, contentWarningReason, - MediaCompressor.intToCompressorQuality(mediaQuality), + CompressorQuality.fromSlider(mediaQuality), server, account, - context, + uploader, stripMetadata = stripMetadata, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) @@ -474,30 +475,31 @@ class NewPublicMessageViewModel : if (results.allGood) { val urls = results.successful.mapNotNull { state -> - if (state.result is UploadOrchestrator.OrchestratorResult.NIP95Result) { - val nip95 = account.createNip95(state.result.bytes, headerInfo = state.result.fileHeader, alt, contentWarningReason) + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.NIP95Result) { + val nip95 = account.createNip95(uploaded.bytes, headerInfo = uploaded.fileHeader, alt, contentWarningReason) nip95attachments = nip95attachments + nip95 val note = nip95.let { it1 -> account.consumeNip95(it1.first, it1.second) } note?.let { "nostr:" + it.toNEvent() } - } else if (state.result is UploadOrchestrator.OrchestratorResult.ServerResult) { + } else if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { val iMeta = - IMetaTagBuilder(state.result.url) + IMetaTagBuilder(uploaded.url) .apply { - hash(state.result.fileHeader.hash) - size(state.result.fileHeader.size) - state.result.fileHeader.mimeType + hash(uploaded.fileHeader.hash) + size(uploaded.fileHeader.size) + uploaded.fileHeader.mimeType ?.let { mimeType(it) } - state.result.fileHeader.dim + uploaded.fileHeader.dim ?.let { dims(it) } - state.result.fileHeader.blurHash + uploaded.fileHeader.blurHash ?.let { blurhash(it.blurhash) } - state.result.fileHeader.thumbHash + uploaded.fileHeader.thumbHash ?.let { thumbhash(it.thumbhash) } - state.result.magnet?.let { magnet(it) } - state.result.uploadedHash?.let { originalHash(it) } + uploaded.magnet?.let { magnet(it) } + uploaded.uploadedHash?.let { originalHash(it) } alt?.let { alt(it) } contentWarningReason?.let { sensitiveContent(contentWarningReason) } @@ -505,7 +507,7 @@ class NewPublicMessageViewModel : iMetaAttachments.replace(iMeta.url, iMeta) - state.result.url + uploaded.url } else { null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt index 173f7abcdd..bf80d12fbb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/pictures/NewPictureButton.kt @@ -51,8 +51,8 @@ import com.vitorpamplona.amethyst.commons.resources.ic_compose import com.vitorpamplona.amethyst.commons.resources.new_picture import com.vitorpamplona.amethyst.commons.resources.take_a_picture import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt index 8a0c2c99ba..4566c8f96b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt @@ -28,10 +28,11 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.podcasts.V4VSplitEditorState import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent import com.vitorpamplona.quartz.nipXXPodcasting20.metadata.Podcasting20PodcastMetadata @@ -168,9 +169,10 @@ class EditPodcastShowViewModel : ViewModel() { ), coverOrchestrator = coverOrch, server = server, - quality = MediaCompressor.intToCompressorQuality(mediaQualitySlider.value), + quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, appContext = context.applicationContext, + uploader = accountViewModel.host.mediaUploader, ) isSending.value = true @@ -186,7 +188,7 @@ class EditPodcastShowViewModel : ViewModel() { quality = snapshot.quality, stripMetadata = snapshot.stripMetadata, alt = snapshot.content.title, - context = snapshot.appContext, + uploader = snapshot.uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) } @@ -219,9 +221,10 @@ class EditPodcastShowViewModel : ViewModel() { val content: Podcasting20PodcastMetadata.Content, val coverOrchestrator: MultiOrchestrator?, val server: ServerName?, - val quality: com.vitorpamplona.amethyst.service.uploads.CompressorQuality, + val quality: CompressorQuality, val stripMetadata: Boolean, val appContext: Context, + val uploader: MediaUploader, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt index 45a242d68c..51cd3f9789 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt @@ -90,8 +90,8 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_episode_video_label import com.vitorpamplona.amethyst.commons.resources.podcast_new_episode import com.vitorpamplona.amethyst.commons.resources.podcast_publishing_banner import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt index 72f55d4a5f..d19af8e576 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt @@ -29,10 +29,11 @@ import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.podcasts.V4VSplitEditorState import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent import com.vitorpamplona.quartz.podcasts.PodcastAudio @@ -205,9 +206,10 @@ class NewPodcastEpisodeViewModel : ViewModel() { existingCoverUrl = coverUrl.value.trim().ifBlank { null }, existingAudioUrl = audioUrl.value.trim(), server = server, - quality = MediaCompressor.intToCompressorQuality(mediaQualitySlider.value), + quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, appContext = context.applicationContext, + uploader = accountViewModel.host.mediaUploader, ) isSending.value = true @@ -265,9 +267,10 @@ class NewPodcastEpisodeViewModel : ViewModel() { val existingCoverUrl: String?, val existingAudioUrl: String, val server: ServerName, - val quality: com.vitorpamplona.amethyst.service.uploads.CompressorQuality, + val quality: CompressorQuality, val stripMetadata: Boolean, val appContext: Context, + val uploader: MediaUploader, ) private suspend fun performParallelUploads(snapshot: Snapshot): Pair = @@ -294,7 +297,7 @@ class NewPodcastEpisodeViewModel : ViewModel() { quality = snapshot.quality, stripMetadata = snapshot.stripMetadata, alt = snapshot.title.ifBlank { null }, - context = snapshot.appContext, + uploader = snapshot.uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt index dc9bba01bd..3cf81e31a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt @@ -71,7 +71,7 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_upload_cta import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_upload_hint import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_url_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt index d1e649b0ba..ba63ff107b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt @@ -26,10 +26,10 @@ import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers @@ -110,7 +110,7 @@ class NewPodcastTrailerViewModel : ViewModel() { val seasonSnap = season.value.trim().toIntOrNull() val mimeSnap = pickedMimeType.value val mediaSnap = media.value - val quality = MediaCompressor.intToCompressorQuality(mediaQualitySlider.value) + val quality = CompressorQuality.fromSlider(mediaQualitySlider.value) val strip = stripMetadata.value val appContext = context.applicationContext @@ -127,7 +127,7 @@ class NewPodcastTrailerViewModel : ViewModel() { quality = quality, stripMetadata = strip, alt = titleSnap.ifBlank { null }, - context = appContext, + uploader = accountViewModel.host.mediaUploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt index 5198d8b943..43cdbf5921 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt @@ -25,11 +25,13 @@ import android.media.MediaMetadataRetriever import android.net.Uri import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality -import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import java.time.ZoneId import java.time.ZonedDateTime import java.time.format.DateTimeFormatter @@ -58,7 +60,7 @@ object PodcastComposerMedia { quality: CompressorQuality, stripMetadata: Boolean, alt: String?, - context: Context, + uploader: MediaUploader, onStrippingFailed: suspend () -> Boolean, ): String { val res = @@ -68,12 +70,12 @@ object PodcastComposerMedia { mediaQuality = quality, server = server, account = account, - context = context, + uploader = uploader, useH265 = false, stripMetadata = stripMetadata, onStrippingFailed = onStrippingFailed, ) - if (!res.allGood) throw UploadException(kind, formatUploadErrors(res.errors, context)) + if (!res.allGood) throw UploadException(kind, formatUploadErrors(res.errors)) return firstUploadedUrl(res.successful) ?: throw UploadException(kind, "Server didn't return a URL for the uploaded $kind.") } @@ -83,10 +85,7 @@ object PodcastComposerMedia { .firstNotNullOfOrNull { it.result as? UploadOrchestrator.OrchestratorResult.ServerResult } ?.url - private suspend fun formatUploadErrors( - errors: List, - context: Context, - ): String = + private suspend fun formatUploadErrors(errors: List): String = errors .map { loadStringRes(it.errorResource, *it.params) } .distinct() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt index 5bc79a1bec..234c89f792 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/shorts/NewShortVideoButton.kt @@ -52,8 +52,8 @@ import com.vitorpamplona.amethyst.commons.resources.ic_compose import com.vitorpamplona.amethyst.commons.resources.new_short_video import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt index 1709d2341d..1d10a9eab3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/url/UrlPostScreen.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.core.net.toUri import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.cache.LocalCache -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.Nav import com.vitorpamplona.amethyst.ui.note.nip22Comments.CommentPostViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt index 77f429b696..fa0450900f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/NewVideoFeedButton.kt @@ -52,8 +52,8 @@ import com.vitorpamplona.amethyst.commons.resources.new_media import com.vitorpamplona.amethyst.commons.resources.record_a_video import com.vitorpamplona.amethyst.commons.resources.take_a_picture import com.vitorpamplona.amethyst.commons.resources.upload_image +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.painterRes import com.vitorpamplona.amethyst.commons.ui.stringRes diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt index ed8b30593e..59a2470b09 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaTest.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.ui.actions.uploads import android.net.Uri -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import io.mockk.mockk import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/MediaCompressorTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/MediaCompressorTest.kt index b0fd8b73c5..9ff95b81a9 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/MediaCompressorTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/MediaCompressorTest.kt @@ -24,7 +24,7 @@ import android.content.Context import android.net.Uri import android.os.Looper import com.davotoula.lightcompressor.VideoCompressor -import com.vitorpamplona.amethyst.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.ui.components.util.MediaCompressorFileUtils import id.zelory.compressor.Compressor diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.android.kt b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.android.kt similarity index 94% rename from commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.android.kt rename to commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.android.kt index 41ef7a7878..5a7e949672 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.android.kt +++ b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.android.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.commons.ui.actions.uploads +package com.vitorpamplona.amethyst.commons.service.uploads import android.net.Uri diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/CompressorQuality.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/CompressorQuality.kt new file mode 100644 index 0000000000..246f907736 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/CompressorQuality.kt @@ -0,0 +1,43 @@ +/* + * 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.uploads + +enum class CompressorQuality { + VERY_LOW, + LOW, + MEDIUM, + HIGH, + VERY_HIGH, + UNCOMPRESSED, + ; + + companion object { + /** The media-quality slider's position (0 low … 3 uncompressed) as a quality. */ + fun fromSlider(position: Int): CompressorQuality = + when (position) { + 0 -> LOW + 1 -> MEDIUM + 2 -> HIGH + 3 -> UNCOMPRESSED + else -> MEDIUM + } + } +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaCompressorResult.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaCompressorResult.kt new file mode 100644 index 0000000000..e1d63e4e8b --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaCompressorResult.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.service.uploads + +/** The file to upload after compression: the original when nothing was compressed. */ +class MediaCompressorResult( + val uri: MediaUri, + val contentType: String?, + val size: Long?, +) diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUploader.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUploader.kt new file mode 100644 index 0000000000..a20a1de7bc --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUploader.kt @@ -0,0 +1,124 @@ +/* + * 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.uploads + +import com.vitorpamplona.amethyst.commons.model.Account +import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName +import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.utils.ciphers.NostrCipher + +/** + * The platform's media pipeline: compresses, strips metadata from, optionally encrypts, and + * uploads a picked file to a NIP-95, NIP-96 or Blossom server, reporting each stage on + * [progress]. Reached through `AccountViewModelHost.mediaUploader`; [UploadOrchestrator] and + * [MultiOrchestrator] are the shared front ends composers use. + */ +interface MediaUploader { + suspend fun upload( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + server: ServerName, + account: Account, + useH265: Boolean, + stripMetadata: Boolean, + onStrippingFailed: suspend () -> Boolean, + convertGifToMp4: Boolean, + forcedSigner: NostrSigner?, + ): UploadingState.UploadingFinalState + + suspend fun uploadEncrypted( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + encrypt: NostrCipher, + server: ServerName, + account: Account, + useH265: Boolean, + stripMetadata: Boolean, + onStrippingFailed: suspend () -> Boolean, + convertGifToMp4: Boolean, + forcedSigner: NostrSigner?, + ): UploadingState.UploadingFinalState + + /** Compresses [uri] when [compressionQuality] or [convertGifToMp4] asks for it, else returns it as is. */ + suspend fun compressIfNeeded( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + compressionQuality: CompressorQuality, + useH265: Boolean, + convertGifToMp4: Boolean, + ): MediaCompressorResult +} + +/** No media pipeline on this front end yet: every upload fails with a clear message. */ +object UnavailableMediaUploader : MediaUploader { + private const val REASON = "Uploads are not available on this platform yet" + + override suspend fun upload( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + server: ServerName, + account: Account, + useH265: Boolean, + stripMetadata: Boolean, + onStrippingFailed: suspend () -> Boolean, + convertGifToMp4: Boolean, + forcedSigner: NostrSigner?, + ): UploadingState.UploadingFinalState = progress.error(UploadError.FAILED_TO_UPLOAD_MEDIA, REASON) + + override suspend fun uploadEncrypted( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + encrypt: NostrCipher, + server: ServerName, + account: Account, + useH265: Boolean, + stripMetadata: Boolean, + onStrippingFailed: suspend () -> Boolean, + convertGifToMp4: Boolean, + forcedSigner: NostrSigner?, + ): UploadingState.UploadingFinalState = progress.error(UploadError.FAILED_TO_UPLOAD_MEDIA, REASON) + + override suspend fun compressIfNeeded( + progress: UploadOrchestrator, + uri: MediaUri, + mimeType: String?, + compressionQuality: CompressorQuality, + useH265: Boolean, + convertGifToMp4: Boolean, + ): MediaCompressorResult = MediaCompressorResult(uri, mimeType, null) +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt similarity index 95% rename from commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt index fd3fca4400..f7459cf988 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.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.commons.ui.actions.uploads +package com.vitorpamplona.amethyst.commons.service.uploads /** * A picked file's address as the platform hands it over: Android's `content://` [android.net.Uri] diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MultiOrchestrator.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MultiOrchestrator.kt index 38547b435f..7958b58003 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MultiOrchestrator.kt @@ -18,20 +18,18 @@ * 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.uploads +package com.vitorpamplona.amethyst.commons.service.uploads -import android.content.Context import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.utils.ciphers.NostrCipher import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch @@ -67,7 +65,7 @@ class MultiOrchestrator( mediaQuality: CompressorQuality, server: ServerName, account: Account, - context: Context, + uploader: MediaUploader, useH265: Boolean = false, stripMetadata: Boolean = true, onStrippingFailed: suspend () -> Boolean = { true }, @@ -86,7 +84,7 @@ class MultiOrchestrator( mediaQuality, server, account, - context, + uploader, useH265, stripMetadata, onStrippingFailed, @@ -109,7 +107,7 @@ class MultiOrchestrator( cipher: NostrCipher, server: ServerName, account: Account, - context: Context, + uploader: MediaUploader, useH265: Boolean = false, stripMetadata: Boolean = true, onStrippingFailed: suspend () -> Boolean = { true }, @@ -129,7 +127,7 @@ class MultiOrchestrator( cipher, server, account, - context, + uploader, useH265, stripMetadata, onStrippingFailed, diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/SelectedMedia.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/SelectedMedia.kt new file mode 100644 index 0000000000..195105ebf1 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/SelectedMedia.kt @@ -0,0 +1,65 @@ +/* + * 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.uploads + +import androidx.compose.runtime.Stable + +@Stable +class SelectedMedia( + val uri: MediaUri, + val mimeType: String?, +) { + fun isImage() = mimeType?.startsWith("image") + + fun isGif() = mimeType?.equals("image/gif", ignoreCase = true) == true + + fun isVideo() = mimeType?.startsWith("video") + + fun isAudio() = mimeType?.startsWith("audio") + + fun isNotMedia() = + mimeType?.let { + !(it.startsWith("image") || it.startsWith("video") || it.startsWith("audio")) + } ?: true + + fun isDocument() = mimeType == "application/pdf" + + /** + * Returns true if [MediaCompressor.compress] would actually compress this file when a + * non-UNCOMPRESSED quality is selected. AVIF, GIF, SVG, and unknown MIME types pass + * through MediaCompressor unchanged — the compression-quality slider has no effect on + * them, so the UI hides the slider when no selected files are compressible. + * + * Keep this in sync with the branching in MediaCompressor.compress() — if either side + * drifts, the UI will lie to the user. + */ + fun isCompressible(): Boolean { + val mt = mimeType?.lowercase() ?: return false + return when { + mt.startsWith("video") -> true + mt.startsWith("image") -> + !mt.contains("gif") && + !mt.contains("svg") && + !isAvif(mt) + else -> false + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaProcessing.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/SelectedMediaProcessing.kt similarity index 86% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaProcessing.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/SelectedMediaProcessing.kt index 8f5335300e..c07ae92cfa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectedMediaProcessing.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/SelectedMediaProcessing.kt @@ -18,10 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.uploads - -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectedMedia -import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator +package com.vitorpamplona.amethyst.commons.service.uploads class SelectedMediaProcessing( val media: SelectedMedia, diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadError.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadError.kt new file mode 100644 index 0000000000..48168136b1 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadError.kt @@ -0,0 +1,35 @@ +/* + * 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.uploads + +/** Why an upload failed. The UI names it with `UploadingState.Error.errorResource`. */ +enum class UploadError { + MEDIA_TOO_BIG_FOR_NIP95, + COULD_NOT_CHECK_DOWNLOADED_FILE, + COULD_NOT_OPEN_COMPRESSED_FILE, + LOGIN_WITH_PRIVATE_KEY, + FAILED_TO_UPLOAD_MEDIA, + BLOSSOM_PAYMENT_REQUIRED, + SERVER_DID_NOT_PROVIDE_URL, + COULD_NOT_DOWNLOAD_FROM_SERVER, + AVIF_METADATA_STRIP_FAILED, + UPLOAD_CANCELLED, +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadOrchestrator.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadOrchestrator.kt new file mode 100644 index 0000000000..dfd672ce8b --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadOrchestrator.kt @@ -0,0 +1,121 @@ +/* + * 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.uploads + +import com.vitorpamplona.amethyst.commons.model.Account +import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName +import com.vitorpamplona.amethyst.commons.service.upload.FileHeader +import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.utils.ciphers.NostrCipher +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map + +/** + * One file's upload, as the composer sees it: its progress and final state. The work itself is + * the [MediaUploader]'s, which reports each stage back through [updateState]. + */ +class UploadOrchestrator { + val progress = MutableStateFlow(0.0) + val progressState = MutableStateFlow(UploadingState.Ready) + + val isUploading = + progressState.map { + progressState.value !is UploadingState.Ready && progressState.value !is UploadingState.Error && progressState.value !is UploadingState.Finished + } + + fun error( + error: UploadError, + vararg params: String, + ) = UploadingState.Error(error, params).also { updateState(0.0, it) } + + fun finish(result: OrchestratorResult) = + UploadingState + .Finished(result) + .also { updateState(1.0, it) } + + fun updateState( + newProgress: Double, + newState: UploadingState, + ) { + progress.value = newProgress + progressState.value = newState + } + + suspend fun compressIfNeeded( + uri: MediaUri, + mimeType: String?, + compressionQuality: CompressorQuality, + uploader: MediaUploader, + useH265: Boolean = false, + convertGifToMp4: Boolean = false, + ): MediaCompressorResult = uploader.compressIfNeeded(this, uri, mimeType, compressionQuality, useH265, convertGifToMp4) + + suspend fun upload( + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + server: ServerName, + account: Account, + uploader: MediaUploader, + useH265: Boolean = false, + stripMetadata: Boolean = true, + onStrippingFailed: suspend () -> Boolean = { true }, + convertGifToMp4: Boolean = false, + forcedSigner: NostrSigner? = null, + ): UploadingState.UploadingFinalState = uploader.upload(this, uri, mimeType, alt, contentWarningReason, compressionQuality, server, account, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) + + suspend fun uploadEncrypted( + uri: MediaUri, + mimeType: String?, + alt: String?, + contentWarningReason: String?, + compressionQuality: CompressorQuality, + encrypt: NostrCipher, + server: ServerName, + account: Account, + uploader: MediaUploader, + useH265: Boolean = false, + stripMetadata: Boolean = true, + onStrippingFailed: suspend () -> Boolean = { true }, + convertGifToMp4: Boolean = false, + forcedSigner: NostrSigner? = null, + ): UploadingState.UploadingFinalState = uploader.uploadEncrypted(this, uri, mimeType, alt, contentWarningReason, compressionQuality, encrypt, server, account, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) + + sealed class OrchestratorResult { + class NIP95Result( + val fileHeader: FileHeader, + val bytes: ByteArray, + val mimeTypeBeforeEncryption: String?, + val hashBeforeEncryption: String?, + ) : OrchestratorResult() + + class ServerResult( + val fileHeader: FileHeader, + val url: String, + val magnet: String?, + val uploadedHash: String?, + val mimeTypeBeforeEncryption: String?, + val hashBeforeEncryption: String?, + ) : OrchestratorResult() + } +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadingState.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadingState.kt new file mode 100644 index 0000000000..0c861368ae --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/UploadingState.kt @@ -0,0 +1,46 @@ +/* + * 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.uploads + +sealed class UploadingState { + data object Ready : UploadingState() + + data object Compressing : UploadingState() + + data object Uploading : UploadingState() + + data object ServerProcessing : UploadingState() + + data object Downloading : UploadingState() + + data object Hashing : UploadingState() + + sealed class UploadingFinalState : UploadingState() + + class Finished( + val result: UploadOrchestrator.OrchestratorResult, + ) : UploadingFinalState() + + class Error( + val error: UploadError, + val params: Array, + ) : UploadingFinalState() +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt index 1ef92d7047..8f497e176e 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelHost.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.viewmodels import com.vitorpamplona.amethyst.commons.service.lnurl.LnurlHttpTransport import com.vitorpamplona.amethyst.commons.service.pow.PoWJobFailure +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.tor.MoneyOpRelayRouting import com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -66,6 +67,9 @@ interface AccountViewModelHost { /** NIP-42 authentication state per relay, across every logged-in account. */ val relayAuthState: StateFlow> + /** Compresses, strips, encrypts and uploads picked media for the composers. */ + val mediaUploader: MediaUploader + /** Public keys of every account saved on this device. */ val savedAccounts: Flow> diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt similarity index 95% rename from commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.ios.kt rename to commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt index f9a5d2cf33..eebaa3945a 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.ios.kt +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.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.commons.ui.actions.uploads +package com.vitorpamplona.amethyst.commons.service.uploads actual abstract class MediaUri { actual abstract override fun toString(): String diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt similarity index 95% rename from commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.jvm.kt rename to commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt index f9a5d2cf33..eebaa3945a 100644 --- a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/MediaUri.jvm.kt +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.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.commons.ui.actions.uploads +package com.vitorpamplona.amethyst.commons.service.uploads actual abstract class MediaUri { actual abstract override fun toString(): String diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt index 44aa42fa16..443a941c23 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt @@ -30,6 +30,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import java.util.concurrent.atomic.AtomicBoolean diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt index b7aa2f604a..20af3c3130 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.android.kt @@ -28,7 +28,7 @@ import androidx.compose.foundation.content.consume import androidx.compose.foundation.content.contentReceiver import androidx.compose.foundation.content.hasMediaType import androidx.compose.ui.Modifier -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri @OptIn(ExperimentalFoundationApi::class) actual fun Modifier.imageContentReceiver(onImage: (MediaUri, String?) -> Unit): Modifier = diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt index 922bbc3537..9b96226c67 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.commons.ui.actions.uploads import androidx.compose.foundation.layout.height import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -35,53 +34,11 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.upload_image -import com.vitorpamplona.amethyst.commons.service.uploads.isAvif +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.commons.ui.stringRes import kotlinx.collections.immutable.ImmutableList -@Stable -class SelectedMedia( - val uri: MediaUri, - val mimeType: String?, -) { - fun isImage() = mimeType?.startsWith("image") - - fun isGif() = mimeType?.equals("image/gif", ignoreCase = true) == true - - fun isVideo() = mimeType?.startsWith("video") - - fun isAudio() = mimeType?.startsWith("audio") - - fun isNotMedia() = - mimeType?.let { - !(it.startsWith("image") || it.startsWith("video") || it.startsWith("audio")) - } ?: true - - fun isDocument() = mimeType == "application/pdf" - - /** - * Returns true if [MediaCompressor.compress] would actually compress this file when a - * non-UNCOMPRESSED quality is selected. AVIF, GIF, SVG, and unknown MIME types pass - * through MediaCompressor unchanged — the compression-quality slider has no effect on - * them, so the UI hides the slider when no selected files are compressible. - * - * Keep this in sync with the branching in MediaCompressor.compress() — if either side - * drifts, the UI will lie to the user. - */ - fun isCompressible(): Boolean { - val mt = mimeType?.lowercase() ?: return false - return when { - mt.startsWith("video") -> true - mt.startsWith("image") -> - !mt.contains("gif") && - !mt.contains("svg") && - !isAvif(mt) - else -> false - } - } -} - @Composable fun SelectFromGallery( isUploading: Boolean, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.kt index d603ebb1b1..eeabee2c7a 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.kt @@ -21,7 +21,7 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.ui.Modifier -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri /** * Accepts images pasted or dragged into the field (keyboard GIFs, stickers, clipboard images), diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadErrorText.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadErrorText.kt new file mode 100644 index 0000000000..6ffa633dda --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadErrorText.kt @@ -0,0 +1,52 @@ +/* + * 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.ui.uploads + +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed +import com.vitorpamplona.amethyst.commons.resources.blossom_payment_required +import com.vitorpamplona.amethyst.commons.resources.could_not_check_downloaded_file +import com.vitorpamplona.amethyst.commons.resources.could_not_download_from_the_server +import com.vitorpamplona.amethyst.commons.resources.could_not_open_the_compressed_file +import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media +import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_upload +import com.vitorpamplona.amethyst.commons.resources.media_too_big_for_nip95 +import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.resources.upload_cancelled +import com.vitorpamplona.amethyst.commons.service.uploads.UploadError +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState +import org.jetbrains.compose.resources.StringResource + +/** The message template for this upload failure; fill it with [UploadingState.Error.params]. */ +val UploadingState.Error.errorResource: StringResource + get() = + when (error) { + UploadError.MEDIA_TOO_BIG_FOR_NIP95 -> Res.string.media_too_big_for_nip95 + UploadError.COULD_NOT_CHECK_DOWNLOADED_FILE -> Res.string.could_not_check_downloaded_file + UploadError.COULD_NOT_OPEN_COMPRESSED_FILE -> Res.string.could_not_open_the_compressed_file + UploadError.LOGIN_WITH_PRIVATE_KEY -> Res.string.login_with_a_private_key_to_be_able_to_upload + UploadError.FAILED_TO_UPLOAD_MEDIA -> Res.string.failed_to_upload_media + UploadError.BLOSSOM_PAYMENT_REQUIRED -> Res.string.blossom_payment_required + UploadError.SERVER_DID_NOT_PROVIDE_URL -> Res.string.server_did_not_provide_a_url_after_uploading + UploadError.COULD_NOT_DOWNLOAD_FROM_SERVER -> Res.string.could_not_download_from_the_server + UploadError.AVIF_METADATA_STRIP_FAILED -> Res.string.avif_metadata_strip_failed + UploadError.UPLOAD_CANCELLED -> Res.string.upload_cancelled + } diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt index 0b211f11fa..8c661fd52a 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.ui.actions.uploads import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt index 6c08448555..43da45fe05 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.ios.kt @@ -21,6 +21,6 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.ui.Modifier -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri actual fun Modifier.imageContentReceiver(onImage: (MediaUri, String?) -> Unit): Modifier = this diff --git a/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt index a7b91243e5..4ac1c41d37 100644 --- a/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt +++ b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/viewmodels/AccountViewModelPreviews.jvmAndroid.kt @@ -37,6 +37,8 @@ import com.vitorpamplona.amethyst.commons.service.http.EmptyRoleBasedHttpClientB import com.vitorpamplona.amethyst.commons.service.http.EncryptionKeyCache import com.vitorpamplona.amethyst.commons.service.lnurl.LnurlHttpTransport import com.vitorpamplona.amethyst.commons.service.pow.PoWJobFailure +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.UnavailableMediaUploader import com.vitorpamplona.amethyst.commons.state.UiSettingsState import com.vitorpamplona.amethyst.commons.tor.MoneyOpRelayRouting import com.vitorpamplona.amethyst.commons.tor.TorRelayEvaluation @@ -198,6 +200,7 @@ private object PreviewAccountViewModelHost : AccountViewModelHost { override val relayStats = RelayStats(EmptyNostrClient()) override val torRelayEvaluation: StateFlow = MutableStateFlow(TorRelayEvaluation(TorRelaySettings())) override val relayAuthState: StateFlow> = MutableStateFlow(persistentMapOf()) + override val mediaUploader: MediaUploader = UnavailableMediaUploader override val websocketBuilder = BasicOkHttpWebSocket.Builder { OkHttpClient() } override val lnurlTransport = LnurlHttpTransport { throw IllegalStateException("Previews do not reach the network") } override val moneyOpRelays = MoneyOpRelayRouting.None diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt index 0b211f11fa..8c661fd52a 100644 --- a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.ui.actions.uploads import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt index 6c08448555..43da45fe05 100644 --- a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ImageContentReceiver.jvm.kt @@ -21,6 +21,6 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.ui.Modifier -import com.vitorpamplona.amethyst.commons.ui.actions.uploads.MediaUri +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri actual fun Modifier.imageContentReceiver(onImage: (MediaUri, String?) -> Unit): Modifier = this From ccf1b7dce97666f6abe8c29894e32492ebb25a57 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 15:25:21 +0000 Subject: [PATCH 13/20] refactor: single-image uploads go through the shared orchestrator Add commonsUI uploadToDefaultServer(media, account, uploader, onError) and use it in place of the five hand-rolled strip/compress/NIP-96/Blossom copies in the profile, emoji pack, emoji pack metadata, relay group metadata and long-form cover uploaders. Drop the Context plumbing the music and podcast composers no longer need. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../ui/actions/NewUserMetadataScreen.kt | 6 +- .../ui/actions/NewUserMetadataViewModel.kt | 103 ++---------------- .../relayGroup/RelayGroupMetadataScreen.kt | 4 +- .../relayGroup/RelayGroupMetadataViewModel.kt | 98 +---------------- .../nip23LongForm/LongFormPostViewModel.kt | 34 +----- .../emojipacks/display/AddEmojiDialog.kt | 4 +- .../emojipacks/display/EmojiPackViewModel.kt | 101 ++--------------- .../list/metadata/EmojiPackMetadataScreen.kt | 4 +- .../metadata/EmojiPackMetadataViewModel.kt | 100 +---------------- .../loggedIn/music/NewMusicPlaylistScreen.kt | 3 - .../music/NewMusicPlaylistViewModel.kt | 8 +- .../loggedIn/music/NewMusicTrackScreen.kt | 1 - .../loggedIn/music/NewMusicTrackViewModel.kt | 7 +- .../authoring/EditPodcastShowScreen.kt | 4 +- .../authoring/EditPodcastShowViewModel.kt | 8 +- .../authoring/NewPodcastEpisodeScreen.kt | 2 +- .../authoring/NewPodcastEpisodeViewModel.kt | 7 +- .../authoring/NewPodcastTrailerScreen.kt | 3 +- .../authoring/NewPodcastTrailerViewModel.kt | 7 +- .../loggedIn/profile/header/ProfileHeader.kt | 4 +- .../ui/uploads/UploadToDefaultServer.kt | 90 +++++++++++++++ 21 files changed, 136 insertions(+), 462 deletions(-) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt index 0dc92bd0ad..9098ab7834 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt @@ -46,7 +46,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.unit.dp @@ -91,7 +90,6 @@ fun NewUserMetadataScreen( ) { val postViewModel: NewUserMetadataViewModel = viewModel() postViewModel.init(accountViewModel) - val context = LocalContext.current val socialExpanded = rememberSaveable { mutableStateOf(false) } @@ -213,7 +211,7 @@ fun NewUserMetadataScreen( tint = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(start = 5.dp), ) { - postViewModel.uploadForPicture(it, context, onError = accountViewModel.toastManager::toast) + postViewModel.uploadForPicture(it, accountViewModel.host.mediaUploader, onError = accountViewModel.toastManager::toast) } }, singleLine = true, @@ -238,7 +236,7 @@ fun NewUserMetadataScreen( tint = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(start = 5.dp), ) { - postViewModel.uploadForBanner(it, context, onError = accountViewModel.toastManager::toast) + postViewModel.uploadForBanner(it, accountViewModel.host.mediaUploader, onError = accountViewModel.toastManager::toast) } }, singleLine = true, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index 31c146955c..d1de32b34a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -20,33 +20,19 @@ */ package com.vitorpamplona.amethyst.ui.actions -import android.content.Context import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType -import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed -import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled -import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia -import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.uploads.uploadToDefaultServer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MetadataStripper -import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader -import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader import com.vitorpamplona.quartz.nip39ExtIdentities.GitHubIdentity import com.vitorpamplona.quartz.nip39ExtIdentities.MastodonIdentity import com.vitorpamplona.quartz.nip39ExtIdentities.TwitterIdentity import com.vitorpamplona.quartz.nip39ExtIdentities.identityClaims -import kotlin.coroutines.cancellation.CancellationException class NewUserMetadataViewModel : ViewModel() { private lateinit var accountViewModel: AccountViewModel @@ -164,13 +150,13 @@ class NewUserMetadataViewModel : ViewModel() { fun uploadForPicture( uri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, ) { accountViewModel.launchSigner { upload( galleryUri = uri, - context = context, + uploader = uploader, onError = onError, )?.let { picture.value = it @@ -180,13 +166,13 @@ class NewUserMetadataViewModel : ViewModel() { fun uploadForBanner( uri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, ) { accountViewModel.launchSigner { upload( galleryUri = uri, - context = context, + uploader = uploader, onError = onError, )?.let { banner.value = it @@ -196,14 +182,14 @@ class NewUserMetadataViewModel : ViewModel() { fun uploadPictureAndSave( uri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, ) { load() accountViewModel.launchSigner { upload( galleryUri = uri, - context = context, + uploader = uploader, onError = onError, )?.let { picture.value = it @@ -215,81 +201,12 @@ class NewUserMetadataViewModel : ViewModel() { private suspend fun upload( galleryUri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, ): String? { isUploadingImageForPicture = true - - val strippingResult = - if (account.settings.stripLocationOnUpload) { - try { - MetadataStripper.strip(galleryUri.uri, galleryUri.mimeType, context.applicationContext) - } catch (e: AvifMetadataNotVerifiableException) { - isUploadingImageForPicture = false - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.avif_metadata_strip_failed, e.message ?: e.javaClass.simpleName), - ) - return null - } - } else { - null - } - - val sourceUri = - if (account.settings.stripLocationOnUpload && - strippingResult != null && - !strippingResult.stripped - ) { - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.metadata_strip_failed_upload_cancelled), - ) - return null - } else { - strippingResult?.uri ?: galleryUri.uri - } - - val compResult = MediaCompressor().compress(sourceUri, galleryUri.mimeType, CompressorQuality.MEDIUM, context.applicationContext) - return try { - val result = - if (account.settings.defaultFileServer.type == ServerType.NIP96) { - Nip96Uploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = accountViewModel.httpClientBuilder::okHttpClientForUploads, - onProgress = {}, - httpAuth = account::createHTTPAuthorization, - context = context, - ) - } else { - BlossomUploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = accountViewModel.httpClientBuilder::okHttpClientForUploads, - httpAuth = account::createBlossomUploadAuth, - context = context, - ) - } - - if (result.url == null) { - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading)) - } - - result.url - } catch (e: Exception) { - if (e is CancellationException) throw e - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) - null + uploadToDefaultServer(galleryUri, account, uploader, onError) } finally { isUploadingImageForPicture = false } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt index b19f9b0bfb..e22a0582ec 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataScreen.kt @@ -60,7 +60,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -211,7 +210,6 @@ private fun RelayGroupMetadataScaffold( // existing group already implies a working relay, so callers pass true. nip29Support: Boolean? = true, ) { - val context = LocalContext.current val scrollState = rememberScrollState() // NIP-29 §Subgroups relay support detection: `"nip29": { "subgroups": true }` in the NIP-11. @@ -236,7 +234,7 @@ private fun RelayGroupMetadataScaffold( val onSubmit: () -> Unit = { viewModel.submit( - context = context, + uploader = accountViewModel.host.mediaUploader, onSuccess = onSuccess, onError = accountViewModel.toastManager::toast, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt index ab1c218a90..a5ded4051f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/relayGroup/RelayGroupMetadataViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.relayGroup -import android.content.Context import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -29,29 +28,17 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect -import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed -import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events -import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_upload -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.read_only_user -import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.uploads.uploadToDefaultServer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MetadataStripper -import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader -import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader import com.vitorpamplona.quartz.buzz.workspace.BUZZ_CHANNEL_TYPE_FORUM import com.vitorpamplona.quartz.buzz.workspace.BUZZ_CHANNEL_TYPE_STREAM import com.vitorpamplona.quartz.buzz.workspace.newBuzzChannelId @@ -61,7 +48,6 @@ import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.utils.RandomInstance import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlin.coroutines.cancellation.CancellationException /** * Backs the create/edit NIP-29 group metadata screens. Holds the full editable metadata @@ -221,7 +207,7 @@ class RelayGroupMetadataViewModel : ViewModel() { } fun submit( - context: Context, + uploader: MediaUploader, onSuccess: () -> Unit, onError: (String, String) -> Unit, ) { @@ -231,7 +217,7 @@ class RelayGroupMetadataViewModel : ViewModel() { try { val local = pickedMedia if (local != null) { - val uploadedUrl = uploadImage(local, context, onError) ?: return@launch + val uploadedUrl = uploadImage(local, uploader, onError) ?: return@launch picture.value = TextFieldValue(uploadedUrl) pickedMedia = null } @@ -311,79 +297,7 @@ class RelayGroupMetadataViewModel : ViewModel() { private suspend fun uploadImage( galleryUri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, - ): String? { - val sourceUri = - try { - if (account.settings.stripLocationOnUpload) { - val result = MetadataStripper.strip(galleryUri.uri, galleryUri.mimeType, context.applicationContext) - if (!result.stripped) { - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.metadata_strip_failed_upload_cancelled), - ) - return null - } - result.uri - } else { - galleryUri.uri - } - } catch (e: AvifMetadataNotVerifiableException) { - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.avif_metadata_strip_failed, e.message ?: e.javaClass.simpleName), - ) - return null - } - val compResult = MediaCompressor().compress(sourceUri, galleryUri.mimeType, CompressorQuality.MEDIUM, context.applicationContext) - - return try { - val result = - if (account.settings.defaultFileServer.type == ServerType.NIP96) { - Nip96Uploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - onProgress = {}, - httpAuth = account::createHTTPAuthorization, - context = context, - ) - } else { - BlossomUploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - httpAuth = account::createBlossomUploadAuth, - context = context, - ) - } - - result.url ?: run { - onError( - loadStringRes(Res.string.failed_to_upload_media_no_details), - loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading), - ) - null - } - } catch (_: SignerExceptions.ReadOnlyException) { - onError( - loadStringRes(Res.string.failed_to_upload_media_no_details), - loadStringRes(Res.string.login_with_a_private_key_to_be_able_to_upload), - ) - null - } catch (e: Exception) { - if (e is CancellationException) throw e - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) - null - } - } + ): String? = uploadToDefaultServer(galleryUri, account, uploader, onError) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt index 5514129da4..600be530c4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt @@ -51,7 +51,6 @@ import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events import com.vitorpamplona.amethyst.commons.resources.read_only_user -import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading import com.vitorpamplona.amethyst.commons.service.pow.PoWReplay import com.vitorpamplona.amethyst.commons.service.upload.MediaUploadTracker import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation @@ -61,7 +60,6 @@ import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator -import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.note.creators.messagefield.IMessageField import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState @@ -72,6 +70,7 @@ import com.vitorpamplona.amethyst.commons.ui.text.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.ui.text.onUiThread import com.vitorpamplona.amethyst.commons.ui.text.replaceCurrentWord import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource +import com.vitorpamplona.amethyst.commons.ui.uploads.uploadToDefaultServer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.LocationState import com.vitorpamplona.amethyst.ui.note.creators.location.ILocationGrabber @@ -487,36 +486,7 @@ class LongFormPostViewModel : galleryUri: SelectedMedia, uploader: MediaUploader, onError: (String, String) -> Unit, - ): String? { - val state = - UploadOrchestrator().upload( - uri = galleryUri.uri, - mimeType = galleryUri.mimeType, - alt = null, - contentWarningReason = null, - compressionQuality = CompressorQuality.MEDIUM, - server = account.settings.defaultFileServer, - account = account, - uploader = uploader, - ) - - return when (state) { - is UploadingState.Finished -> { - val uploaded = state.result - if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { - uploaded.url - } else { - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading)) - null - } - } - is UploadingState.Error -> { - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(state.errorResource, *state.params)) - null - } - else -> null - } - } + ): String? = uploadToDefaultServer(galleryUri, account, uploader, onError) fun upload( alt: String?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt index 4b1b0f326d..d3ec2ff3ac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt @@ -41,7 +41,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -119,7 +118,6 @@ fun AddEmojiDialog( onValueChange = { url = it }, label = { Text(stringRes(Res.string.emoji_url_label)) }, leadingIcon = { - val context = LocalContext.current SelectSingleFromGallery( isUploading = viewModel.isUploadingEmojiImage, tint = MaterialTheme.colorScheme.onSurfaceVariant, @@ -127,7 +125,7 @@ fun AddEmojiDialog( ) { selected -> viewModel.uploadEmojiImage( uri = selected, - context = context, + uploader = accountViewModel.host.mediaUploader, onUploaded = { uploadedUrl -> url = uploadedUrl }, onError = accountViewModel.toastManager::toast, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt index 751bce6682..7337b98241 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.emojipacks.display -import android.content.Context import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -28,31 +27,15 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType -import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed -import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details -import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_upload -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled -import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia -import com.vitorpamplona.amethyst.commons.ui.loadStringRes -import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MetadataStripper -import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader -import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader -import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions +import com.vitorpamplona.amethyst.commons.ui.uploads.uploadToDefaultServer import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import kotlin.coroutines.cancellation.CancellationException @Stable class EmojiPackViewModel( @@ -86,14 +69,14 @@ class EmojiPackViewModel( fun uploadEmojiImage( uri: SelectedMedia, - context: Context, + uploader: MediaUploader, onUploaded: (String) -> Unit, onError: (String, String) -> Unit, ) { viewModelScope.launch(Dispatchers.IO) { upload( uri, - context, + uploader, onUploading = { isUploadingEmojiImage = it }, onUploaded = onUploaded, onError = onError, @@ -103,83 +86,15 @@ class EmojiPackViewModel( private suspend fun upload( galleryUri: SelectedMedia, - context: Context, + uploader: MediaUploader, onUploading: (Boolean) -> Unit, onUploaded: (String) -> Unit, onError: (String, String) -> Unit, ) { onUploading(true) - - val sourceUri = - try { - if (account.settings.stripLocationOnUpload) { - val result = MetadataStripper.strip(galleryUri.uri, galleryUri.mimeType, context.applicationContext) - if (!result.stripped) { - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.metadata_strip_failed_upload_cancelled), - ) - onUploading(false) - return - } - result.uri - } else { - galleryUri.uri - } - } catch (e: AvifMetadataNotVerifiableException) { - onUploading(false) - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.avif_metadata_strip_failed, e.message ?: e.javaClass.simpleName), - ) - return - } - val compResult = MediaCompressor().compress(sourceUri, galleryUri.mimeType, CompressorQuality.MEDIUM, context.applicationContext) - - try { - val result = - if (account.settings.defaultFileServer.type == ServerType.NIP96) { - Nip96Uploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - onProgress = {}, - httpAuth = account::createHTTPAuthorization, - context = context, - ) - } else { - BlossomUploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - httpAuth = account::createBlossomUploadAuth, - context = context, - ) - } - - if (result.url != null) { - onUploading(false) - onUploaded(result.url) - } else { - onUploading(false) - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading)) - } - } catch (_: SignerExceptions.ReadOnlyException) { - onUploading(false) - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.login_with_a_private_key_to_be_able_to_upload)) - } catch (e: Exception) { - if (e is CancellationException) throw e - onUploading(false) - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) - } + val url = uploadToDefaultServer(galleryUri, account, uploader, onError) + onUploading(false) + if (url != null) onUploaded(url) } @Suppress("UNCHECKED_CAST") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt index dcce6ee4e3..432914c28d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt @@ -53,7 +53,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -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.style.TextAlign @@ -109,7 +108,6 @@ private fun EmojiPackMetadataScaffold( accountViewModel: AccountViewModel, nav: INav, ) { - val context = LocalContext.current val scrollState = rememberScrollState() var wantsToPickImage by remember { mutableStateOf(false) } @@ -127,7 +125,7 @@ private fun EmojiPackMetadataScaffold( val onSubmit: () -> Unit = { viewModel.submit( - context = context, + uploader = accountViewModel.host.mediaUploader, onSuccess = { nav.popBack() }, onError = accountViewModel.toastManager::toast, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt index 964abeef53..71c5e6500d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.emojipacks.list.metadata -import android.content.Context import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -29,32 +28,19 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.model.Account -import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.OwnedEmojiPack import com.vitorpamplona.amethyst.commons.resources.Res -import com.vitorpamplona.amethyst.commons.resources.avif_metadata_strip_failed -import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_sign_events -import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_upload -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title -import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.read_only_user -import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading -import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.amethyst.commons.ui.uploads.uploadToDefaultServer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.uploads.AvifMetadataNotVerifiableException -import com.vitorpamplona.amethyst.service.uploads.MediaCompressor -import com.vitorpamplona.amethyst.service.uploads.MetadataStripper -import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader -import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlin.coroutines.cancellation.CancellationException @Stable class EmojiPackMetadataViewModel : ViewModel() { @@ -101,7 +87,7 @@ class EmojiPackMetadataViewModel : ViewModel() { } fun submit( - context: Context, + uploader: MediaUploader, onSuccess: () -> Unit, onError: (String, String) -> Unit, ) { @@ -111,7 +97,7 @@ class EmojiPackMetadataViewModel : ViewModel() { try { val local = pickedMedia if (local != null) { - val uploadedUrl = uploadImage(local, context, onError) + val uploadedUrl = uploadImage(local, uploader, onError) if (uploadedUrl == null) { isWorking = false return@launch @@ -165,81 +151,7 @@ class EmojiPackMetadataViewModel : ViewModel() { private suspend fun uploadImage( galleryUri: SelectedMedia, - context: Context, + uploader: MediaUploader, onError: (String, String) -> Unit, - ): String? { - val sourceUri = - try { - if (account.settings.stripLocationOnUpload) { - val result = MetadataStripper.strip(galleryUri.uri, galleryUri.mimeType, context.applicationContext) - if (!result.stripped) { - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.metadata_strip_failed_upload_cancelled), - ) - return null - } - result.uri - } else { - galleryUri.uri - } - } catch (e: AvifMetadataNotVerifiableException) { - onError( - loadStringRes(Res.string.metadata_strip_failed_title), - loadStringRes(Res.string.avif_metadata_strip_failed, e.message ?: e.javaClass.simpleName), - ) - return null - } - val compResult = MediaCompressor().compress(sourceUri, galleryUri.mimeType, CompressorQuality.MEDIUM, context.applicationContext) - - return try { - val result = - if (account.settings.defaultFileServer.type == ServerType.NIP96) { - Nip96Uploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - onProgress = {}, - httpAuth = account::createHTTPAuthorization, - context = context, - ) - } else { - BlossomUploader().upload( - uri = compResult.uri, - contentType = compResult.contentType, - size = compResult.size, - alt = null, - sensitiveContent = null, - serverBaseUrl = account.settings.defaultFileServer.baseUrl, - okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder::okHttpClientForUploads, - httpAuth = account::createBlossomUploadAuth, - context = context, - ) - } - - if (result.url != null) { - result.url - } else { - onError( - loadStringRes(Res.string.failed_to_upload_media_no_details), - loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading), - ) - null - } - } catch (_: SignerExceptions.ReadOnlyException) { - onError( - loadStringRes(Res.string.failed_to_upload_media_no_details), - loadStringRes(Res.string.login_with_a_private_key_to_be_able_to_upload), - ) - null - } catch (e: Exception) { - if (e is CancellationException) throw e - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) - null - } - } + ): String? = uploadToDefaultServer(galleryUri, account, uploader, onError) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt index a02d8290e2..c220bb01b7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt @@ -57,7 +57,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -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.style.TextOverflow @@ -116,7 +115,6 @@ fun NewMusicPlaylistScreen( nav: INav, ) { val vm: NewMusicPlaylistViewModel = viewModel() - val context = LocalContext.current LaunchedEffect(accountViewModel) { vm.init(accountViewModel, editDTag) @@ -155,7 +153,6 @@ fun NewMusicPlaylistScreen( onPost = { if (!vm.isValid() || isBusy) return@SendingTopBar vm.saveAndPublish( - context = context, accountViewModel = accountViewModel, ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt index 2c1d87d16d..6f637bed77 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.music -import android.content.Context import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account @@ -185,10 +184,7 @@ class NewMusicPlaylistViewModel : ViewModel() { * runs on [AccountViewModel.viewModelScope] via `launchSigner`, so it keeps running even if * the user leaves the screen — they get a toast notification when it finishes either way. */ - fun saveAndPublish( - context: Context, - accountViewModel: AccountViewModel, - ) { + fun saveAndPublish(accountViewModel: AccountViewModel) { if (isSending.value) return // double-tap guard if (!isValid()) return @@ -218,7 +214,6 @@ class NewMusicPlaylistViewModel : ViewModel() { quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, loadedEvent = loadedEvent, - appContext = context.applicationContext, uploader = accountViewModel.host.mediaUploader, ) @@ -266,7 +261,6 @@ class NewMusicPlaylistViewModel : ViewModel() { val quality: CompressorQuality, val stripMetadata: Boolean, val loadedEvent: MusicPlaylistEvent?, - val appContext: Context, val uploader: MediaUploader, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt index a8b4306067..ed2e74d639 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt @@ -154,7 +154,6 @@ fun NewMusicTrackScreen( onPost = { if (!vm.isValid() || isBusy) return@SendingTopBar vm.saveAndPublish( - context = context, accountViewModel = accountViewModel, ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt index e4b010cf45..98dceec6da 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt @@ -259,10 +259,7 @@ class NewMusicTrackViewModel : ViewModel() { * Errors are reported through the global toast manager rather than a callback because * the screen may already be gone by the time we know the outcome. */ - fun saveAndPublish( - context: Context, - accountViewModel: AccountViewModel, - ) { + fun saveAndPublish(accountViewModel: AccountViewModel) { if (isSending.value) return // double-tap guard // Snapshot every input the upload needs into immutable locals BEFORE launching, so @@ -293,7 +290,6 @@ class NewMusicTrackViewModel : ViewModel() { quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, loadedEvent = loadedEvent, - appContext = context.applicationContext, uploader = accountViewModel.host.mediaUploader, ) @@ -365,7 +361,6 @@ class NewMusicTrackViewModel : ViewModel() { val quality: CompressorQuality, val stripMetadata: Boolean, val loadedEvent: MusicTrackEvent?, - val appContext: Context, val uploader: MediaUploader, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt index 0c21bcadec..c0ab56b896 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt @@ -44,7 +44,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp @@ -97,7 +96,6 @@ fun EditPodcastShowScreen( nav: INav, ) { val vm: EditPodcastShowViewModel = viewModel() - val context = LocalContext.current LaunchedEffect(accountViewModel) { vm.init(accountViewModel) } @@ -124,7 +122,7 @@ fun EditPodcastShowScreen( isActive = { vm.isValid() && !isBusy }, onPost = { if (!vm.isValid() || isBusy) return@SendingTopBar - vm.saveAndPublish(context, accountViewModel) + vm.saveAndPublish(accountViewModel) }, ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt index 4566c8f96b..2ea653ed89 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring -import android.content.Context import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account @@ -130,10 +129,7 @@ class EditPodcastShowViewModel : ViewModel() { fun isValid(): Boolean = title.value.isNotBlank() - fun saveAndPublish( - context: Context, - accountViewModel: AccountViewModel, - ) { + fun saveAndPublish(accountViewModel: AccountViewModel) { if (isSending.value) return val coverOrch = coverMedia.value @@ -171,7 +167,6 @@ class EditPodcastShowViewModel : ViewModel() { server = server, quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, - appContext = context.applicationContext, uploader = accountViewModel.host.mediaUploader, ) @@ -223,7 +218,6 @@ class EditPodcastShowViewModel : ViewModel() { val server: ServerName?, val quality: CompressorQuality, val stripMetadata: Boolean, - val appContext: Context, val uploader: MediaUploader, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt index 51cd3f9789..346b646aba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt @@ -152,7 +152,7 @@ fun NewPodcastEpisodeScreen( isActive = { vm.isValid() && !isBusy }, onPost = { if (!vm.isValid() || isBusy) return@SendingTopBar - vm.saveAndPublish(context, accountViewModel) + vm.saveAndPublish(accountViewModel) }, ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt index d19af8e576..f0e20f62ae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt @@ -174,10 +174,7 @@ class NewPodcastEpisodeViewModel : ViewModel() { /** Valid with a title and a resolvable audio source (picked file or a pasted URL). */ fun isValid(): Boolean = title.value.isNotBlank() && (audioMedia.value != null || audioUrl.value.isNotBlank()) - fun saveAndPublish( - context: Context, - accountViewModel: AccountViewModel, - ) { + fun saveAndPublish(accountViewModel: AccountViewModel) { if (isSending.value) return val server = selectedServer.value @@ -208,7 +205,6 @@ class NewPodcastEpisodeViewModel : ViewModel() { server = server, quality = CompressorQuality.fromSlider(mediaQualitySlider.value), stripMetadata = stripMetadata.value, - appContext = context.applicationContext, uploader = accountViewModel.host.mediaUploader, ) @@ -269,7 +265,6 @@ class NewPodcastEpisodeViewModel : ViewModel() { val server: ServerName, val quality: CompressorQuality, val stripMetadata: Boolean, - val appContext: Context, val uploader: MediaUploader, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt index 3cf81e31a5..9d35569431 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt @@ -88,7 +88,6 @@ fun NewPodcastTrailerScreen( nav: INav, ) { val vm: NewPodcastTrailerViewModel = viewModel() - val context = LocalContext.current LaunchedEffect(accountViewModel) { vm.init(accountViewModel) } StrippingFailureDialog(vm.strippingFailureConfirmation) @@ -112,7 +111,7 @@ fun NewPodcastTrailerScreen( isActive = { vm.isValid() && !isBusy }, onPost = { if (!vm.isValid() || isBusy) return@SendingTopBar - vm.saveAndPublish(context, accountViewModel) + vm.saveAndPublish(accountViewModel) }, ) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt index ba63ff107b..a57f1e1f57 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring -import android.content.Context import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.commons.model.Account @@ -91,10 +90,7 @@ class NewPodcastTrailerViewModel : ViewModel() { fun isValid(): Boolean = title.value.isNotBlank() && (media.value != null || url.value.isNotBlank()) - fun saveAndPublish( - context: Context, - accountViewModel: AccountViewModel, - ) { + fun saveAndPublish(accountViewModel: AccountViewModel) { if (isSending.value) return val server = selectedServer.value if (server == null) { @@ -112,7 +108,6 @@ class NewPodcastTrailerViewModel : ViewModel() { val mediaSnap = media.value val quality = CompressorQuality.fromSlider(mediaQualitySlider.value) val strip = stripMetadata.value - val appContext = context.applicationContext isSending.value = true accountViewModel.launchSigner { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt index e88fa61fef..331148ac2e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt @@ -49,7 +49,6 @@ 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.platform.LocalContext import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel @@ -194,7 +193,6 @@ private fun ProfilePictureUploadButton( ) { val postViewModel: NewUserMetadataViewModel = viewModel() postViewModel.init(accountViewModel) - val context = LocalContext.current var showGallerySelect by remember { mutableStateOf(false) } if (showGallerySelect) { @@ -202,7 +200,7 @@ private fun ProfilePictureUploadButton( onImageUri = { media -> showGallerySelect = false if (media != null) { - postViewModel.uploadPictureAndSave(media, context, accountViewModel.toastManager::toast) + postViewModel.uploadPictureAndSave(media, accountViewModel.host.mediaUploader, accountViewModel.toastManager::toast) } }, ) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt new file mode 100644 index 0000000000..3b6fa75390 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt @@ -0,0 +1,90 @@ +/* + * 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.ui.uploads + +import com.vitorpamplona.amethyst.commons.model.Account +import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details +import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_upload +import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading +import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator +import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.loadStringRes +import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions +import kotlinx.coroutines.CancellationException + +/** + * Compresses, strips (when the account asks for it) and uploads one picked file to the account's + * default media server, returning the hosted URL. Every failure, a metadata-stripping one + * included, is reported through [onError] with a user-facing title and message, and yields null. + * + * For single-image fields (avatars, banners, pack and cover images) that only need a URL back. + */ +suspend fun uploadToDefaultServer( + media: SelectedMedia, + account: Account, + uploader: MediaUploader, + onError: (title: String, message: String) -> Unit, + quality: CompressorQuality = CompressorQuality.MEDIUM, +): String? { + val state = + try { + UploadOrchestrator().upload( + uri = media.uri, + mimeType = media.mimeType, + alt = null, + contentWarningReason = null, + compressionQuality = quality, + server = account.settings.defaultFileServer, + account = account, + uploader = uploader, + stripMetadata = account.settings.stripLocationOnUpload, + onStrippingFailed = { false }, + ) + } catch (_: SignerExceptions.ReadOnlyException) { + onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.login_with_a_private_key_to_be_able_to_upload)) + return null + } catch (e: Exception) { + if (e is CancellationException) throw e + onError(loadStringRes(Res.string.failed_to_upload_media_no_details), e.message ?: e::class.simpleName ?: "") + return null + } + + return when (state) { + is UploadingState.Finished -> { + val uploaded = state.result + if (uploaded is UploadOrchestrator.OrchestratorResult.ServerResult) { + uploaded.url + } else { + onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(Res.string.server_did_not_provide_a_url_after_uploading)) + null + } + } + + is UploadingState.Error -> { + onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(state.errorResource, *state.params)) + null + } + } +} From 815749b15838af4d44925e16e3ef6bf19a1876db Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 15:41:49 +0000 Subject: [PATCH 14/20] refactor: move profile editor and emoji pack screens to commonsUI The upload port freed NewUserMetadataScreen, EmojiPackScreen and EmojiPackMetadataScreen (7 files with their view models and the add-emoji dialog). Record the upload port and the next blockers in the one-UI plan. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/ui/navigation/AppNavigation.kt | 6 ++--- .../loggedIn/profile/header/ProfileHeader.kt | 2 +- .../2026-09-27-one-ui-android-desktop.md | 23 +++++++++++++++---- .../ui/actions/NewUserMetadataScreen.kt | 6 ++--- .../ui/actions/NewUserMetadataViewModel.kt | 2 +- .../emojipacks/display/AddEmojiDialog.kt | 2 +- .../emojipacks/display/EmojiPackScreen.kt | 6 ++--- .../emojipacks/display/EmojiPackViewModel.kt | 12 +++++++--- .../list/metadata/EmojiPackMetadataScreen.kt | 6 ++--- .../metadata/EmojiPackMetadataViewModel.kt | 3 ++- 10 files changed, 44 insertions(+), 24 deletions(-) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/actions/NewUserMetadataScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/actions/NewUserMetadataViewModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/emojipacks/display/EmojiPackScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt (90%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt (98%) 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 ba81057a14..072c4a17a2 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 @@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.user.LocalUserFinderAccoun import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.invalid_nip19_uri import com.vitorpamplona.amethyst.commons.resources.invalid_nip19_uri_description +import com.vitorpamplona.amethyst.commons.ui.actions.NewUserMetadataScreen import com.vitorpamplona.amethyst.commons.ui.layouts.LocalScreenLayout import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.LocalTabReselectCoordinator import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.TabReselectCoordinator @@ -102,7 +103,9 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.Communi import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.contactList.ContactListUsersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.drafts.DraftListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.browse.BrowseEmojiSetsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.display.EmojiPackScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.list.ListOfEmojiPacksScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.list.metadata.EmojiPackMetadataScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.EmojiPackSelectionScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.MyEmojiListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.FavoriteAppsScreen @@ -184,7 +187,6 @@ import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.RelayA import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.compose.DisplayNotifyMessages import com.vitorpamplona.amethyst.service.resourceusage.DisplayResourceUsageAlert import com.vitorpamplona.amethyst.service.resourceusage.ScreenTimeIntegrator -import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataScreen import com.vitorpamplona.amethyst.ui.actions.bolt12Offers.Bolt12OffersScreen import com.vitorpamplona.amethyst.ui.actions.mediaServers.AllMediaServersScreen import com.vitorpamplona.amethyst.ui.actions.mediaServers.BlossomBlobManagerScreen @@ -266,8 +268,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabLayer import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabPreloader import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabThemeWatcher import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.FavoriteAppManifestPreloader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.emojipacks.display.EmojiPackScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.emojipacks.list.metadata.EmojiPackMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.NostrAppScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.FollowPackFeedScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.list.FollowPacksScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt index 331148ac2e..cc5d516355 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/header/ProfileHeader.kt @@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserPicture import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.upload_image 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.navigation.navs.INav @@ -69,7 +70,6 @@ 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.actions.NewUserMetadataViewModel import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.header.apps.UserAppRecommendationsFeedViewModel import kotlinx.coroutines.launch 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 095146935d..cfdef91a50 100644 --- a/commons/plans/2026-09-27-one-ui-android-desktop.md +++ b/commons/plans/2026-09-27-one-ui-android-desktop.md @@ -413,11 +413,24 @@ assemblers, `EventSync`, …). Packages are renamed on the way: - `MediaMimeTypes` moved to commons. - **Wave 6 moved:** the NIP-46 signer screen. The other cuts clear blockers that sit behind the upload stack. - - **Next:** the post-composer upload stack is now the main blocker, holding about 30 - screens. `UploadOrchestrator`/`MultiOrchestrator` thread an Android `Context` through - compression, metadata stripping, encryption and the NIP-95/NIP-96/Blossom uploaders, - and the composer view models call them directly. The video player stack (media3) is - next at about 22. Smaller exits remain: the Android-only `AccountViewModel` actions, + - **Upload port (2026-09-29):** + - The upload model is in `commons.service.uploads`: `UploadOrchestrator`, + `MultiOrchestrator`, `UploadingState`, `CompressorQuality`, `MediaCompressorResult` + and the `MediaUploader` port. + - Errors are an `UploadError` enum. commonsUI maps it to a string (`errorResource`). + - The Android pipeline (compression, metadata stripping, encryption, the + NIP-95/NIP-96/Blossom uploaders) stays in the app as `AndroidMediaUploader`. It is + reached through `AccountViewModelHost.mediaUploader`. + - Composer view models take a `MediaUploader` instead of a `Context`. + - Five view models had their own copy of strip, compress and upload for a single + image. They now call the shared `uploadToDefaultServer`. + - Moved: the profile editor, the emoji pack screen and the emoji pack metadata screen + (7 files). + - **Next:** most composer screens still exit through `ShowImageUploadItem` (the upload + preview, which uses Android media APIs to show a thumbnail) and `SelectFromFiles`. + After those comes the per-composer state: `ShortNotePostViewModel`, `PreviewState` + and the location pickers. The video player stack (media3) holds about 22 screens. + Smaller exits remain: the Android-only `AccountViewModel` actions, `CalendarTimeFormat`, `NestUiState`, `NappletLauncher`, `UpdateZapAmountDialog` (biometrics), the `java.time` pickers and the language list. 7. **Navigation**: the library swap, then `AppNavigation` + rail + drawer + bottom bar. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/NewUserMetadataScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/NewUserMetadataScreen.kt index 9098ab7834..8fe1295de8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/NewUserMetadataScreen.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions +package com.vitorpamplona.amethyst.commons.ui.actions import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically @@ -49,7 +49,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization 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.resources.Res @@ -75,6 +74,7 @@ import com.vitorpamplona.amethyst.commons.resources.twitter_proof_url_template import com.vitorpamplona.amethyst.commons.resources.username import com.vitorpamplona.amethyst.commons.resources.website_url import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectSingleFromGallery +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar @@ -88,7 +88,7 @@ fun NewUserMetadataScreen( nav: INav, accountViewModel: AccountViewModel, ) { - val postViewModel: NewUserMetadataViewModel = viewModel() + val postViewModel: NewUserMetadataViewModel = rememberViewModel { NewUserMetadataViewModel() } postViewModel.init(accountViewModel) val socialExpanded = rememberSaveable { mutableStateOf(false) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/NewUserMetadataViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/NewUserMetadataViewModel.kt index d1de32b34a..a2e4f3721a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/NewUserMetadataViewModel.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions +package com.vitorpamplona.amethyst.commons.ui.actions import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt index d3ec2ff3ac..2d688a396c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/AddEmojiDialog.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.emojipacks.display +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.display import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/EmojiPackScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/EmojiPackScreen.kt index 9fb240fd82..c8c628b4d7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/EmojiPackScreen.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.emojipacks.display +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.display import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background @@ -54,7 +54,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import coil3.compose.AsyncImage import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -68,6 +67,7 @@ import com.vitorpamplona.amethyst.commons.resources.quick_action_delete 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.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ShorterTopAppBar @@ -84,7 +84,7 @@ fun EmojiPackScreen( nav: INav, ) { val viewModel: EmojiPackViewModel = - viewModel( + rememberViewModel( factory = EmojiPackViewModel.Initializer(accountViewModel.account, packIdentifier), ) EmojiPackScreenView(viewModel, accountViewModel, nav) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt similarity index 90% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt index 7337b98241..99c20b5ea9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/display/EmojiPackViewModel.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.emojipacks.display +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.display import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -27,15 +27,18 @@ import androidx.compose.runtime.setValue 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.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.uploads.uploadToDefaultServer import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrlTag import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlin.reflect.KClass @Stable class EmojiPackViewModel( @@ -101,7 +104,10 @@ class EmojiPackViewModel( class Initializer( val account: Account, val packIdentifier: String, - ) : ViewModelProvider.NewInstanceFactory() { - override fun create(modelClass: Class): T = EmojiPackViewModel(account, packIdentifier) as T + ) : ViewModelProvider.Factory { + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = EmojiPackViewModel(account, packIdentifier) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt index 432914c28d..99e38027f9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataScreen.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.emojipacks.list.metadata +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.list.metadata import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -58,7 +58,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import coil3.compose.AsyncImage import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -71,6 +70,7 @@ import com.vitorpamplona.amethyst.commons.resources.emoji_pack_upload_image_cta import com.vitorpamplona.amethyst.commons.resources.emoji_pack_upload_image_hint import com.vitorpamplona.amethyst.commons.resources.new_emoji_pack import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar @@ -85,7 +85,7 @@ fun EmojiPackMetadataScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val viewModel: EmojiPackMetadataViewModel = viewModel() + val viewModel: EmojiPackMetadataViewModel = rememberViewModel { EmojiPackMetadataViewModel() } viewModel.init(accountViewModel) if (packIdentifier != null) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt index 71c5e6500d..3047a3a289 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/emojipacks/list/metadata/EmojiPackMetadataViewModel.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.emojipacks.list.metadata +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.list.metadata import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf @@ -40,6 +40,7 @@ import com.vitorpamplona.amethyst.commons.ui.uploads.uploadToDefaultServer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.launch @Stable From 03626cef48399bbf50b9c7de6793f9d780ae12c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 18:30:00 +0000 Subject: [PATCH 15/20] refactor: share the upload preview and move six composer screens - The upload preview (ShowImageUploadItem) moves to commonsUI. Its video frame comes from a new expect rememberVideoThumbnail (Android decodes a frame; desktop and iOS fall back to the player). - GallerySelectSingle takes imagesOnly; Concord's icon and banner pickers use it and a long notice instead of the activity launcher and Toast. ConcordImageUploader takes the SelectedMedia instead of a Uri + Context. - edgeToEdgeDialogProperties replaces the Android-only decorFitsSystemWindows flag for full-window dialogs. - CashuWalletDiscovery moves to commonMain (StateFlow maps and counter in place of ConcurrentHashMap/AtomicInteger). - Moved: badges, new community, new music playlist, Concord create/edit and the Cashu wallet wizard (19 files). The community rules test moves with them to commonsUI jvmTest. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/ui/actions/NewMediaView.kt | 2 +- .../uploads/UploadProgressIndicator.kt | 1 + .../amethyst/ui/navigation/AppNavigation.kt | 14 ++-- .../creators/uploads/ImageVideoDescription.kt | 2 +- .../loggedIn/BottomBarFeedPreloaders.kt | 2 +- .../metadata/ChannelMetadataScreen.kt | 2 +- .../chats/utils/ChatFileUploadDialog.kt | 2 +- .../loggedIn/music/NewMusicTrackScreen.kt | 3 + .../authoring/EditPodcastShowScreen.kt | 4 +- .../authoring/EditPodcastShowViewModel.kt | 2 +- .../authoring/NewPodcastEpisodeScreen.kt | 6 +- .../authoring/NewPodcastTrailerScreen.kt | 4 +- .../relays/blocked/BlockedRelayListView.kt | 2 +- .../broadcast/BroadcastRelayListView.kt | 2 +- .../loggedIn/relays/dm/DMRelayListView.kt | 2 +- .../relays/feeds/RelayFeedsListView.kt | 2 +- .../relays/indexer/IndexerRelayListView.kt | 2 +- .../keyPackage/KeyPackageRelayListView.kt | 2 +- .../relays/local/LocalRelayListView.kt | 2 +- .../nip37/PrivateOutboxRelayListView.kt | 2 +- .../relays/nip65/Nip65RelayListView.kt | 2 +- .../relays/proxy/ProxyRelayListView.kt | 2 +- .../relays/search/SearchRelayListView.kt | 2 +- .../relays/trusted/TrustedRelayListView.kt | 2 +- .../relays/vanish/RequestToVanishScreen.kt | 2 +- .../commons/cashu/CashuWalletDiscovery.kt | 20 ++--- .../actions/uploads/GallerySelect.android.kt | 7 +- .../actions/uploads/VideoThumbnail.android.kt | 74 +++++++++++++++++++ .../SetDialogToEdgeToEdge.android.kt | 12 +++ .../ui/actions/uploads/SelectFromGallery.kt | 7 +- .../ui/actions/uploads/ShowImageUploadItem.kt | 67 +++-------------- .../ui/actions/uploads/VideoThumbnail.kt | 32 ++++++++ .../ui/components/SetDialogToEdgeToEdge.kt | 10 +++ .../ui/screen/loggedIn/badges/BadgesScreen.kt | 4 +- .../ui/screen/loggedIn/badges/BadgesTopBar.kt | 2 +- .../screen/loggedIn/badges/NewBadgeButton.kt | 10 +-- .../BadgesFilterAssemblerSubscription.kt | 2 +- .../loggedIn/badges/post/NewBadgeDialog.kt | 13 +--- .../loggedIn/badges/post/NewBadgeModel.kt | 9 ++- .../concord/ConcordCreateScreen.kt | 2 +- .../concord/ConcordEditScreen.kt | 2 +- .../concord/ConcordImageUploader.kt | 15 ++-- .../concord/ConcordMetadataForm.kt | 49 ++++++------ .../newCommunity/CommunityRulesEditor.kt | 2 +- .../newCommunity/NewCommunityModel.kt | 3 +- .../newCommunity/NewCommunityScreen.kt | 10 +-- .../loggedIn/music/MusicComposerUploadUi.kt | 4 +- .../loggedIn/music/NewMusicPlaylistScreen.kt | 6 +- .../music/NewMusicPlaylistViewModel.kt | 4 +- .../relays/common/RelayUrlEditField.kt | 5 +- .../wallet/wizard/CashuWalletWizardScreen.kt | 6 +- .../wizard/CashuWalletWizardViewModel.kt | 2 +- .../ui/actions/uploads/GallerySelect.ios.kt | 5 +- .../ui/actions/uploads/VideoThumbnail.ios.kt | 29 ++++++++ .../components/SetDialogToEdgeToEdge.ios.kt | 11 +++ .../ui/actions/uploads/GallerySelect.jvm.kt | 5 +- .../ui/actions/uploads/VideoThumbnail.jvm.kt | 29 ++++++++ .../components/SetDialogToEdgeToEdge.jvm.kt | 11 +++ .../NewCommunityModelRulesTest.kt | 2 +- 59 files changed, 361 insertions(+), 180 deletions(-) rename commons/src/{jvmAndroid => commonMain}/kotlin/com/vitorpamplona/amethyst/commons/cashu/CashuWalletDiscovery.kt (92%) create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/actions/uploads/ShowImageUploadItem.kt (85%) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/BadgesScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/BadgesTopBar.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/NewBadgeButton.kt (87%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/datasource/BadgesFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/badges/post/NewBadgeModel.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCreateScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/concord/ConcordEditScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt (94%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt (88%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/communities/newCommunity/CommunityRulesEditor.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/music/MusicComposerUploadUi.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardViewModel.kt (99%) create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.ios.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.jvm.kt rename {amethyst/src/test/java/com/vitorpamplona/amethyst => commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/communities/newCommunity/NewCommunityModelRulesTest.kt (98%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index 24e986c84b..ba41954360 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_description import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer @@ -84,7 +85,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt index 93a48052a0..0a8c4439ea 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/UploadProgressIndicator.kt @@ -51,6 +51,7 @@ import com.vitorpamplona.amethyst.commons.resources.uploading_state_server_proce import com.vitorpamplona.amethyst.commons.resources.uploading_state_uploading import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource 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 072c4a17a2..9a4bea97d8 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 @@ -66,6 +66,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.TabReselectCo import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.apps.recommendations.ProfileAppRecommendationsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BackupConflictReviewScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.BadgesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.award.AwardBadgeScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.profile.ProfileBadgesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.bookmarkgroups.default.BookmarkListScreen @@ -91,6 +92,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.Co import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.cordnGroup.CordnInvitationsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.geohashChat.GeohashTeleportScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordChannelListScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordCreateScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordEditScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordHomeScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordInviteLinksScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.ConcordInviteScreen @@ -100,6 +103,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupThreadsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.CommunityScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity.EditCommunityScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity.NewCommunityScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.contactList.ContactListUsersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.drafts.DraftListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.browse.BrowseEmojiSetsScreen @@ -124,6 +129,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.list.ListOfPe import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.MusicPlaylistsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.MusicTracksScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.NewMusicPlaylistScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.napplets.ConnectedAppsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.newUser.ImportFollowListPickFollowsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.newUser.ImportFollowListSelectUserScreen @@ -179,6 +185,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletDetail import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletReceiveScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletSendScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletTransactionsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.wizard.CashuWalletWizardScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.WorkoutsScreen import com.vitorpamplona.amethyst.commons.ui.uriToRoute import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -212,7 +219,6 @@ import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsQrScreen import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.ArticlesScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.BadgesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.display.BookmarkGroupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.metadata.BookmarkGroupMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.WebAppScreen @@ -240,8 +246,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomByA import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.ChatroomScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.NewGroupDMScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordChannelScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordCreateScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.concord.ConcordEditScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.EphemeralChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.metadata.NewEphemeralChatScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.PublicChatChannelScreen @@ -256,8 +260,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.share.ShareToDMScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessGameScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessLobbyScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.list.CommunitiesScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.newCommunity.EditCommunityScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.newCommunity.NewCommunityScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.LongFormPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.NewProductScreen @@ -293,7 +295,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleL import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.LiveStreamsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.LongsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.AddToMusicPlaylistSheet -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.NewMusicPlaylistScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.NewMusicTrackScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.ConnectedAppDetailScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.napplets.NappletsScreen @@ -337,7 +338,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.VideoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.hls.NewHlsVideoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.wizard.CashuWalletWizardScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.webBookmarks.WebBookmarksScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.NewWorkoutScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness.MyFitnessScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt index d03fc98f99..10d312c1a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt @@ -92,6 +92,7 @@ import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_description import com.vitorpamplona.amethyst.commons.resources.video_codec_h265_label import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer import com.vitorpamplona.amethyst.commons.ui.note.CancelIcon @@ -104,7 +105,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.ai.MLKitImageLabelService -import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import kotlinx.collections.immutable.toImmutableList @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt index 211e7a3027..bbf874cb41 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.key import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.model.navigation.BottomBarEntry import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.datasource.BadgesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.browse.datasource.BrowseEmojiSetsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.datasource.GeocachesFilterAssemblerSubscription @@ -40,7 +41,6 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.dataso import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.datasource.WorkoutsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.datasource.BadgesFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.list.datasource.CommunitiesListFilterAssemblerSubscription diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt index 760b1de02e..c42a93fa4a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/nip28PublicChat/metadata/ChannelMetadataScreen.kt @@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SavingTopBar import com.vitorpamplona.amethyst.commons.ui.note.LoadPublicChatChannel +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer @@ -81,7 +82,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.SettingsCategory import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt index c9e48c904f..488b3b7ea2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/utils/ChatFileUploadDialog.kt @@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.resources.media_compression_quality_me import com.vitorpamplona.amethyst.commons.resources.media_compression_quality_uncompressed import com.vitorpamplona.amethyst.commons.resources.strip_metadata_description import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer @@ -86,7 +87,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt index ed2e74d639..647b2bebbb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackScreen.kt @@ -90,6 +90,9 @@ import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe 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.SendingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.CoverImagePicker +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadInProgressBanner +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadPlaceholder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt index c0ab56b896..d831b4b5f2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt @@ -77,10 +77,10 @@ import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.CoverImagePicker +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadInProgressBanner import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.CoverImagePicker -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadInProgressBanner import kotlinx.collections.immutable.persistentListOf import org.jetbrains.compose.resources.StringResource diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt index 2ea653ed89..3a7ca74282 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt @@ -204,7 +204,7 @@ class EditPodcastShowViewModel : ViewModel() { } catch (t: Throwable) { accountViewModel.toastManager.toast( "Failed to save podcast", - t.message ?: t.javaClass.simpleName, + t.message ?: t::class.simpleName ?: "", ) } finally { withContext(Dispatchers.Main.immediate) { isSending.value = false } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt index 346b646aba..80256104c8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt @@ -95,11 +95,11 @@ import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.CoverImagePicker +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadInProgressBanner +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadPlaceholder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.CoverImagePicker -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadInProgressBanner -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadPlaceholder import kotlinx.collections.immutable.persistentListOf import org.jetbrains.compose.resources.StringResource diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt index 9d35569431..d356eab08f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt @@ -75,10 +75,10 @@ import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadInProgressBanner +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadPlaceholder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadInProgressBanner -import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.UploadPlaceholder /** Composer for a Podcasting-2.0 trailer (`kind:30055`): title, a short audio/video clip, and season. */ @OptIn(ExperimentalMaterial3Api::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt index 6d182feb7b..976150bbe1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/blocked/BlockedRelayListView.kt @@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -42,7 +43,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField @Composable fun BlockedRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt index d4e788b58f..127c8df427 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/broadcast/BroadcastRelayListView.kt @@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -42,7 +43,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField @Composable fun BroadcastRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt index d70f4465c0..8b5680b539 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/dm/DMRelayListView.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -43,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt index 95b795bf36..85949dcc17 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/feeds/RelayFeedsListView.kt @@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -42,7 +43,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField @Composable fun FavoriteRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt index 00cd0a43b4..5360bd9692 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/indexer/IndexerRelayListView.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -43,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt index c9b8305782..8b6e3ad4a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/keyPackage/KeyPackageRelayListView.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -43,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt index 7a3267b385..3580d9c981 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/local/LocalRelayListView.kt @@ -38,6 +38,7 @@ import com.vitorpamplona.amethyst.commons.resources.send_kind0_to_local_relay_de import com.vitorpamplona.amethyst.commons.resources.send_kind0_to_local_relay_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.SettingsRow import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding @@ -48,7 +49,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField @Composable fun LocalRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt index e21b4c6c88..05e0fa25a9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip37/PrivateOutboxRelayListView.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -43,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt index 7d7c173979..36e17be0e1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/nip65/Nip65RelayListView.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -43,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt index eb43732e10..bb6f268bb5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/proxy/ProxyRelayListView.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -43,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt index d76010019f..0b7cdd27a1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/search/SearchRelayListView.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -43,7 +44,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt index 8c0e2410e7..c96d2c8707 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/trusted/TrustedRelayListView.kt @@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.relays.ui.RelayDragState import com.vitorpamplona.amethyst.commons.relays.ui.rememberRelayDragState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding import com.vitorpamplona.amethyst.commons.ui.theme.HorzHalfVertPadding @@ -42,7 +43,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField @Composable fun TrustedRelayList( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt index 96122af4f2..6c2b5e7171 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/vanish/RequestToVanishScreen.kt @@ -95,6 +95,7 @@ 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.DateTimeStyle import com.vitorpamplona.amethyst.commons.ui.note.formatDateTime +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -103,7 +104,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl import com.vitorpamplona.quartz.utils.TimeUtils diff --git a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/cashu/CashuWalletDiscovery.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/cashu/CashuWalletDiscovery.kt similarity index 92% rename from commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/cashu/CashuWalletDiscovery.kt rename to commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/cashu/CashuWalletDiscovery.kt index 71d2e6a214..643dab3a37 100644 --- a/commons/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/cashu/CashuWalletDiscovery.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/cashu/CashuWalletDiscovery.kt @@ -32,17 +32,17 @@ import com.vitorpamplona.quartz.nip61Nutzaps.info.NutzapInfoEvent import com.vitorpamplona.quartz.utils.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.flow.updateAndGet import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.sync.Semaphore -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.cancellation.CancellationException /** @@ -126,9 +126,9 @@ class CashuWalletDiscovery( // Distinct by event id — the same kind:17375 is found on many relays, // but a user with wallets created by different apps will have several // distinct ids. Keep them all; the wizard sorts/picks the newest. - val wallets = ConcurrentHashMap() - val nutzapInfos = ConcurrentHashMap() - val completed = AtomicInteger(0) + val wallets = MutableStateFlow>(emptyMap()) + val nutzapInfos = MutableStateFlow>(emptyMap()) + val completed = MutableStateFlow(0) _state.value = State.Crawling(0, relays.size) @@ -151,16 +151,16 @@ class CashuWalletDiscovery( onEvent = { event -> when (event) { is CashuWalletEvent -> - if (event.pubKey == pubKey) wallets.putIfAbsent(event.id, event) + if (event.pubKey == pubKey) wallets.update { if (event.id in it) it else it + (event.id to event) } is NutzapInfoEvent -> - if (event.pubKey == pubKey) nutzapInfos.putIfAbsent(event.id, event) + if (event.pubKey == pubKey) nutzapInfos.update { if (event.id in it) it else it + (event.id to event) } else -> Unit } }, onRelayComplete = { // Up to MAX_CONCURRENT_RELAYS relays finish at once: a coroutine preempted // between the increment and the write would move the progress backwards. - val done = completed.incrementAndGet() + val done = completed.updateAndGet { it + 1 } _state.update { current -> if (current is State.Crawling && current.relaysCompleted >= done) current else State.Crawling(done, relays.size) } @@ -170,8 +170,8 @@ class CashuWalletDiscovery( _state.value = State.Done( - walletEvents = wallets.values.sortedByDescending { it.createdAt }, - nutzapInfoEvents = nutzapInfos.values.sortedByDescending { it.createdAt }, + walletEvents = wallets.value.values.sortedByDescending { it.createdAt }, + nutzapInfoEvents = nutzapInfos.value.values.sortedByDescending { it.createdAt }, ) } catch (e: CancellationException) { throw e diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt index 443a941c23..a2561364ac 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt @@ -67,7 +67,10 @@ actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { } @Composable -actual fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit) { +actual fun GallerySelectSingle( + imagesOnly: Boolean, + onImageUri: (SelectedMedia?) -> Unit, +) { val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } val resolver = LocalContext.current.contentResolver @@ -89,7 +92,7 @@ actual fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit) { fun LaunchGallery() { SideEffect { if (!hasLaunched.getAndSet(true)) { - launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) + launcher.launch(PickVisualMediaRequest(if (imagesOnly) ActivityResultContracts.PickVisualMedia.ImageOnly else ActivityResultContracts.PickVisualMedia.ImageAndVideo)) } } } diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt new file mode 100644 index 0000000000..57b3a1d9ce --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt @@ -0,0 +1,74 @@ +/* + * 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.ui.actions.uploads + +import android.content.Context +import android.graphics.Bitmap +import android.media.MediaMetadataRetriever +import android.net.Uri +import android.os.Build +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.quartz.utils.Log +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +@Composable +actual fun rememberVideoThumbnail(media: SelectedMedia): ImageBitmap? { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return null + val context = LocalContext.current + val thumb by produceState(null, media) { + value = + withContext(Dispatchers.IO) { + try { + createVideoThumb(context, media.uri)?.asImageBitmap() + } catch (e: Exception) { + if (e is CancellationException) throw e + Log.w("VideoThumbnail", "Couldn't create thumbnail, but the video can be uploaded", e) + null + } + } + } + return thumb +} + +/** + * Creates a bitmap thumbnail from video uri of the scheme type content:// + */ +fun createVideoThumb( + context: Context, + uri: Uri, +): Bitmap? { + try { + val mediaMetadataRetriever = MediaMetadataRetriever() + mediaMetadataRetriever.setDataSource(context, uri) + return mediaMetadataRetriever.frameAtTime + } catch (ex: Exception) { + Log.w("VideoThumbnail", "Couldn't create thumbnail, but the video can be uploaded", ex) + } + return null +} diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.kt index b80738bd5d..0b9c109450 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.android.kt @@ -27,6 +27,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogWindowProvider import androidx.core.view.WindowCompat import com.vitorpamplona.amethyst.commons.ui.theme.isLight @@ -57,3 +58,14 @@ actual fun SetDialogToEdgeToEdge() { } } } + +actual fun edgeToEdgeDialogProperties( + dismissOnBackPress: Boolean, + dismissOnClickOutside: Boolean, +): DialogProperties = + DialogProperties( + usePlatformDefaultWidth = false, + dismissOnBackPress = dismissOnBackPress, + dismissOnClickOutside = dismissOnClickOutside, + decorFitsSystemWindows = false, + ) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt index 9b96226c67..f7e0dea30e 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromGallery.kt @@ -114,6 +114,9 @@ private fun GallerySelectButton( @Composable expect fun GallerySelect(onImageUri: (ImmutableList) -> Unit = {}) -/** Opens the platform's photo/video picker for one item; reports null on cancel. */ +/** Opens the platform's photo/video picker for one item ([imagesOnly]: photos only); reports null on cancel. */ @Composable -expect fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit = {}) +expect fun GallerySelectSingle( + imagesOnly: Boolean = false, + onImageUri: (SelectedMedia?) -> Unit = {}, +) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/ShowImageUploadItem.kt similarity index 85% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/ShowImageUploadItem.kt index f72973fde9..70306318cf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/ShowImageUploadItem.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/ShowImageUploadItem.kt @@ -18,13 +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.actions.uploads +package com.vitorpamplona.amethyst.commons.ui.actions.uploads -import android.content.Context -import android.graphics.Bitmap -import android.media.MediaMetadataRetriever -import android.net.Uri -import android.os.Build import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -47,7 +42,6 @@ import androidx.compose.material3.ProgressIndicatorDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -56,9 +50,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -83,19 +75,16 @@ import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMediaProcessing import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.rememberVideoThumbnail import com.vitorpamplona.amethyst.commons.ui.components.AutoNonlazyGrid import com.vitorpamplona.amethyst.commons.ui.note.CloseIcon +import com.vitorpamplona.amethyst.commons.ui.note.platform.VideoView import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size40Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.playback.composable.VideoView -import com.vitorpamplona.quartz.utils.Log -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch @Composable fun ShowImageUploadGallery( @@ -108,23 +97,6 @@ fun ShowImageUploadGallery( } } -/** - * Creates a bitmap thumbnail from video uri of the scheme type content:// - */ -fun createVideoThumb( - context: Context, - uri: Uri, -): Bitmap? { - try { - val mediaMetadataRetriever = MediaMetadataRetriever() - mediaMetadataRetriever.setDataSource(context, uri) - return mediaMetadataRetriever.frameAtTime - } catch (ex: Exception) { - Log.w("NewPostView", "Couldn't create thumbnail, but the video can be uploaded", ex) - } - return null -} - @Composable fun ShowImageGallery( media: SelectedMedia, @@ -157,30 +129,15 @@ fun ShowImageGallery( ) } - media.isVideo() == true && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> { - var bitmap by remember { mutableStateOf(null) } - val context = LocalContext.current - - LaunchedEffect(key1 = media) { - launch(Dispatchers.IO) { - try { - bitmap = createVideoThumb(context, media.uri) - } catch (e: Exception) { - if (e is CancellationException) throw e - Log.w("NewPostView", "Couldn't create thumbnail, but the video can be uploaded", e) - } - } - } - - if (bitmap != null) { - bitmap?.let { - Image( - bitmap = it.asImageBitmap(), - contentDescription = "some useful description", - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxWidth(), - ) - } + media.isVideo() == true -> { + val thumbnail = rememberVideoThumbnail(media) + if (thumbnail != null) { + Image( + bitmap = thumbnail, + contentDescription = "some useful description", + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxWidth(), + ) } else { VideoView( videoUri = media.uri.toString(), diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.kt new file mode 100644 index 0000000000..c857778e9b --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.kt @@ -0,0 +1,32 @@ +/* + * 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.ui.actions.uploads + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia + +/** + * A frame of the picked video [media], for the upload preview. Null while it decodes, and where + * the platform cannot decode one; the preview then plays the video itself. + */ +@Composable +expect fun rememberVideoThumbnail(media: SelectedMedia): ImageBitmap? diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.kt index ac8b90e9e4..e35401e40f 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties /** * Stretches the enclosing dialog's window over the whole activity and matches its system-bar @@ -28,3 +29,12 @@ import androidx.compose.runtime.Composable */ @Composable expect fun SetDialogToEdgeToEdge() + +/** + * Properties for a full-window dialog drawn edge to edge (pair with [SetDialogToEdgeToEdge]). On + * Android the dialog window also stops fitting the system bars. + */ +expect fun edgeToEdgeDialogProperties( + dismissOnBackPress: Boolean = true, + dismissOnClickOutside: Boolean = true, +): DialogProperties diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/BadgesScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/BadgesScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/BadgesScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/BadgesScreen.kt index 654cac5a9a..f62a2127e5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/BadgesScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/BadgesScreen.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.badges +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -35,8 +35,8 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded 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.badges.datasource.BadgesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.datasource.BadgesFilterAssemblerSubscription @Composable fun BadgesScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/BadgesTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/BadgesTopBar.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/BadgesTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/BadgesTopBar.kt index a98a85ad36..1c1bbe0d06 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/BadgesTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/BadgesTopBar.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.badges +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/NewBadgeButton.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/NewBadgeButton.kt similarity index 87% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/NewBadgeButton.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/NewBadgeButton.kt index 2703e96f57..6b402c46ef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/NewBadgeButton.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/NewBadgeButton.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.badges +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.FloatingActionButton @@ -29,22 +29,22 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.ic_compose import com.vitorpamplona.amethyst.commons.resources.new_badge +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.painterRes +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.post.NewBadgeDialog +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.post.NewBadgeModel import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size26Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.post.NewBadgeDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.post.NewBadgeModel @Composable fun NewBadgeButton(accountViewModel: AccountViewModel) { var showDialog by remember { mutableStateOf(false) } - val postViewModel: NewBadgeModel = viewModel() + val postViewModel: NewBadgeModel = rememberViewModel { NewBadgeModel() } if (showDialog) { NewBadgeDialog( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/datasource/BadgesFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/datasource/BadgesFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/datasource/BadgesFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/datasource/BadgesFilterAssemblerSubscription.kt index 6bdee17125..2f8bb5a52c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/datasource/BadgesFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/datasource/BadgesFilterAssemblerSubscription.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.badges.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt index dd2e9abb71..774c5cc936 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeDialog.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/post/NewBadgeDialog.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.badges.post +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.post import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -60,7 +60,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.mediaServers.DEFAULT_MEDIA_SERVERS @@ -84,9 +83,11 @@ import com.vitorpamplona.amethyst.commons.resources.strip_metadata_description import com.vitorpamplona.amethyst.commons.resources.strip_metadata_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.commons.ui.components.SetDialogToEdgeToEdge import com.vitorpamplona.amethyst.commons.ui.components.TextSpinner import com.vitorpamplona.amethyst.commons.ui.components.TitleExplainer +import com.vitorpamplona.amethyst.commons.ui.components.edgeToEdgeDialogProperties import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar import com.vitorpamplona.amethyst.commons.ui.note.creators.contentWarning.SettingSwitchItem @@ -95,7 +96,6 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -131,12 +131,7 @@ fun NewBadgeDialog( Dialog( onDismissRequest = onClose, - properties = - DialogProperties( - usePlatformDefaultWidth = false, - dismissOnClickOutside = false, - decorFitsSystemWindows = false, - ), + properties = edgeToEdgeDialogProperties(dismissOnClickOutside = false), ) { SetDialogToEdgeToEdge() Scaffold( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/post/NewBadgeModel.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/post/NewBadgeModel.kt index 5bfdc7d5c0..25f3ec1c03 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/post/NewBadgeModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/badges/post/NewBadgeModel.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.badges.post +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.post import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue @@ -46,8 +46,10 @@ import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import com.vitorpamplona.quartz.nip58Badges.definition.tags.ThumbTag import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.launch -import java.util.UUID +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid /** * Drives the "new badge" creation flow: uploads the user-picked image (at the @@ -127,6 +129,7 @@ class NewBadgeModel : ViewModel() { } } + @OptIn(ExperimentalUuidApi::class) private fun uploadUnsafe( uploader: MediaUploader, onSuccess: () -> Unit, @@ -181,7 +184,7 @@ class NewBadgeModel : ViewModel() { val dimensions = uploaded.fileHeader.dim myAccount.sendBadgeDefinition( - badgeId = UUID.randomUUID().toString(), + badgeId = Uuid.random().toString(), name = name.trim(), imageUrl = imageUrl, imageDim = dimensions, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCreateScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCreateScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCreateScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCreateScreen.kt index 5f7549b8fe..bc54acea03 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCreateScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordCreateScreen.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.chats.publicChannels.concord +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordEditScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordEditScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordEditScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordEditScreen.kt index ddf048bd2a..acfe0d0423 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordEditScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordEditScreen.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.chats.publicChannels.concord +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord 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/chats/publicChannels/concord/ConcordImageUploader.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt similarity index 94% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt index 9c33247bb2..bef5c6f91b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordImageUploader.kt @@ -18,16 +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.chats.publicChannels.concord +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord -import android.content.Context -import android.net.Uri import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.DEFAULT_MEDIA_SERVERS import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes @@ -36,6 +35,7 @@ import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.utils.ciphers.AESGCM import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.withContext /** @@ -54,14 +54,13 @@ class ConcordImageUploader( private val account: Account, ) { /** - * Compresses, strips, AES-256-GCM-encrypts and uploads the picked [uri], returning its pointer. + * Compresses, strips, AES-256-GCM-encrypts and uploads the picked [media], returning its pointer. * * Runs on [Dispatchers.IO]: compression, encryption and the upload all block, and callers launch * this from a Compose `rememberCoroutineScope()`, which is Main-dispatched. */ suspend fun uploadEncrypted( - uri: Uri, - context: Context, + media: SelectedMedia, uploader: MediaUploader, ): ImagePointer = withContext(Dispatchers.IO) { @@ -71,8 +70,8 @@ class ConcordImageUploader( val finalState = UploadOrchestrator().uploadEncrypted( - uri = uri, - mimeType = context.contentResolver.getType(uri), + uri = media.uri, + mimeType = media.mimeType, alt = null, contentWarningReason = null, compressionQuality = CompressorQuality.MEDIUM, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt similarity index 88% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt index 7f3ab7b079..ebb6b3fbf2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt @@ -18,12 +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.chats.publicChannels.concord +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord -import android.widget.Toast -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -57,7 +53,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale 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.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -75,17 +70,19 @@ import com.vitorpamplona.amethyst.commons.resources.concord_create_title import com.vitorpamplona.amethyst.commons.resources.concord_edit_banner_hint import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details import com.vitorpamplona.amethyst.commons.resources.remove +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage +import com.vitorpamplona.amethyst.commons.ui.components.rememberLongNotice import com.vitorpamplona.amethyst.commons.ui.components.util.setText import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.commons.ui.platform.rememberConcordImageModel import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.MediumRelayIconModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl @@ -157,29 +154,31 @@ private fun ConcordIconHero( accountViewModel: AccountViewModel, ) { val autoPlayGif by accountViewModel.settings.autoPlayVideosFlow.collectAsStateWithLifecycle() - val context = LocalContext.current + val notice = rememberLongNotice() val scope = rememberCoroutineScope() var uploading by remember { mutableStateOf(false) } val iconModel = rememberConcordImageModel(icon.value, accountViewModel) - val picker = - rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> - if (uri == null) return@rememberLauncherForActivityResult + var picking by remember { mutableStateOf(false) } + if (picking) { + GallerySelectSingle(imagesOnly = true) { media -> + picking = false + if (media == null) return@GallerySelectSingle uploading = true scope.launch { try { - icon.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(uri, context, accountViewModel.host.mediaUploader) + icon.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(media, accountViewModel.host.mediaUploader) } catch (e: CancellationException) { throw e } catch (e: Exception) { Log.w("ConcordImageUpload", "Community icon upload failed", e) - val msg = e.message?.takeIf { it.isNotBlank() } ?: loadStringRes(Res.string.failed_to_upload_media_no_details) - Toast.makeText(context, msg, Toast.LENGTH_LONG).show() + notice.show(e.message?.takeIf { it.isNotBlank() } ?: loadStringRes(Res.string.failed_to_upload_media_no_details)) } finally { uploading = false } } } + } Column(horizontalAlignment = Alignment.CenterHorizontally) { Box( @@ -187,7 +186,7 @@ private fun ConcordIconHero( Modifier .size(104.dp) .clip(CircleShape) - .clickable(enabled = !uploading) { picker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, + .clickable(enabled = !uploading) { picking = true }, contentAlignment = Alignment.Center, ) { RobohashFallbackAsyncImage( @@ -211,7 +210,7 @@ private fun ConcordIconHero( Modifier .padding(top = 8.dp) .clip(CircleShape) - .clickable(enabled = !uploading) { picker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) } + .clickable(enabled = !uploading) { picking = true } .padding(horizontal = 8.dp, vertical = 4.dp), ) } @@ -228,29 +227,31 @@ private fun ConcordBannerHero( banner: MutableState, accountViewModel: AccountViewModel, ) { - val context = LocalContext.current + val notice = rememberLongNotice() val scope = rememberCoroutineScope() var uploading by remember { mutableStateOf(false) } val bannerModel = rememberConcordImageModel(banner.value, accountViewModel) - val picker = - rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> - if (uri == null) return@rememberLauncherForActivityResult + var picking by remember { mutableStateOf(false) } + if (picking) { + GallerySelectSingle(imagesOnly = true) { media -> + picking = false + if (media == null) return@GallerySelectSingle uploading = true scope.launch { try { - banner.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(uri, context, accountViewModel.host.mediaUploader) + banner.value = ConcordImageUploader(accountViewModel.account).uploadEncrypted(media, accountViewModel.host.mediaUploader) } catch (e: CancellationException) { throw e } catch (e: Exception) { Log.w("ConcordImageUpload", "Community banner upload failed", e) - val msg = e.message?.takeIf { it.isNotBlank() } ?: loadStringRes(Res.string.failed_to_upload_media_no_details) - Toast.makeText(context, msg, Toast.LENGTH_LONG).show() + notice.show(e.message?.takeIf { it.isNotBlank() } ?: loadStringRes(Res.string.failed_to_upload_media_no_details)) } finally { uploading = false } } } + } Box( modifier = @@ -259,7 +260,7 @@ private fun ConcordBannerHero( .aspectRatio(3f) .clip(RoundedCornerShape(12.dp)) .background(MaterialTheme.colorScheme.surfaceVariant) - .clickable(enabled = !uploading) { picker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, + .clickable(enabled = !uploading) { picking = true }, contentAlignment = Alignment.Center, ) { if (bannerModel != null) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/CommunityRulesEditor.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/CommunityRulesEditor.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/CommunityRulesEditor.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/CommunityRulesEditor.kt index cb020b11d7..c816965d9b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/CommunityRulesEditor.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/CommunityRulesEditor.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.communities.newCommunity +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt index 0aef57488b..b45bcf09dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityModel.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.communities.newCommunity +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable @@ -56,6 +56,7 @@ import com.vitorpamplona.quartz.nip72ModCommunities.rules.tags.PubkeyRuleTag import com.vitorpamplona.quartz.nip72ModCommunities.rules.tags.WotTag import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.launch import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt index 3afb77c81c..7ae8c550e6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityScreen.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.communities.newCommunity +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -63,7 +63,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import coil3.compose.AsyncImage import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -93,7 +92,9 @@ import com.vitorpamplona.amethyst.commons.resources.remove import com.vitorpamplona.amethyst.commons.resources.save import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelect +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.commons.ui.components.Nip05OrPubkeyLine +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ActionTopBar @@ -103,12 +104,11 @@ import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.ShowU import com.vitorpamplona.amethyst.commons.ui.note.creators.userSuggestions.UserSuggestionState import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoClickableRow +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPage import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery -import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.RelayUrlEditField import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.RelayTag @@ -135,7 +135,7 @@ private fun CommunityFormScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val model: NewCommunityModel = viewModel() + val model: NewCommunityModel = rememberViewModel { NewCommunityModel() } LaunchedEffect(accountViewModel.account) { model.init(accountViewModel.account) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/MusicComposerUploadUi.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/MusicComposerUploadUi.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/MusicComposerUploadUi.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/MusicComposerUploadUi.kt index c535b03b39..4024ff1ad1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/MusicComposerUploadUi.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/MusicComposerUploadUi.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.music +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -51,9 +51,9 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.ShowImageUploadGallery import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.ShowImageUploadGallery import org.jetbrains.compose.resources.StringResource /** diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt index c220bb01b7..17e51187ee 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/NewMusicPlaylistScreen.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.music +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -62,7 +62,6 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview 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.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -95,6 +94,7 @@ import com.vitorpamplona.amethyst.commons.resources.new_music_playlist import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -114,7 +114,7 @@ fun NewMusicPlaylistScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val vm: NewMusicPlaylistViewModel = viewModel() + val vm: NewMusicPlaylistViewModel = rememberViewModel { NewMusicPlaylistViewModel() } LaunchedEffect(accountViewModel) { vm.init(accountViewModel, editDTag) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt index 6f637bed77..fbc2db14f3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/music/NewMusicPlaylistViewModel.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.music +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel @@ -240,7 +240,7 @@ class NewMusicPlaylistViewModel : ViewModel() { } catch (t: Throwable) { accountViewModel.toastManager.toast( "Failed to send playlist", - t.message ?: t.javaClass.simpleName, + t.message ?: t::class.simpleName ?: "", ) } finally { withContext(Dispatchers.Main.immediate) { isSending.value = false } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt index 208d22a47f..27d8cd16eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/relays/common/RelayUrlEditField.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.common import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth @@ -46,6 +46,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.relays.nip11RelayInfo.Nip11CachedRetriever +import com.vitorpamplona.amethyst.commons.relays.nip11RelayInfo.Nip11Fetcher import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.add import com.vitorpamplona.amethyst.commons.resources.add_a_relay @@ -124,7 +125,7 @@ fun RelayUrlEditFieldPreview() { RelayUrlEditField( onNewRelay = {}, relaySuggestions = relaySuggestions, - nip11CachedRetriever = Nip11CachedRetriever { TODO() }, + nip11CachedRetriever = Nip11CachedRetriever(Nip11Fetcher.Offline), modifier = Modifier, accountViewModel = accountViewModel, nav = EmptyNav(), diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt index bbf929d1b3..4edb6d454a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardScreen.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.wallet.wizard +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.wizard import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.animateFloatAsState @@ -67,7 +67,6 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign 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.navigation.Route @@ -99,6 +98,7 @@ import com.vitorpamplona.amethyst.commons.resources.cashu_wizard_single_descript import com.vitorpamplona.amethyst.commons.resources.cashu_wizard_single_title import com.vitorpamplona.amethyst.commons.resources.cashu_wizard_title import com.vitorpamplona.amethyst.commons.resources.cashu_wizard_use_wallet +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.util.formatGrouped @@ -122,7 +122,7 @@ fun CashuWalletWizardScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val viewModel: CashuWalletWizardViewModel = viewModel() + val viewModel: CashuWalletWizardViewModel = rememberViewModel { CashuWalletWizardViewModel() } viewModel.init(accountViewModel) val wizardState by viewModel.wizardState.collectAsState() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardViewModel.kt index 37fec79deb..a76231e1ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/wizard/CashuWalletWizardViewModel.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.wallet.wizard +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.wizard import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt index 8c661fd52a..ec3612ac65 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.ios.kt @@ -34,6 +34,9 @@ actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { } @Composable -actual fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit) { +actual fun GallerySelectSingle( + imagesOnly: Boolean, + onImageUri: (SelectedMedia?) -> Unit, +) { LaunchedEffect(Unit) { onImageUri(null) } } diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.ios.kt new file mode 100644 index 0000000000..3e746b9968 --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.ios.kt @@ -0,0 +1,29 @@ +/* + * 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.ui.actions.uploads + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia + +/** No frame decoder wired yet: the preview plays the video instead. */ +@Composable +actual fun rememberVideoThumbnail(media: SelectedMedia): ImageBitmap? = null diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt index 5cdb61e42b..874770bc15 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.ios.kt @@ -21,6 +21,17 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties @Composable actual fun SetDialogToEdgeToEdge() {} + +actual fun edgeToEdgeDialogProperties( + dismissOnBackPress: Boolean, + dismissOnClickOutside: Boolean, +): DialogProperties = + DialogProperties( + usePlatformDefaultWidth = false, + dismissOnBackPress = dismissOnBackPress, + dismissOnClickOutside = dismissOnClickOutside, + ) diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt index 8c661fd52a..ec3612ac65 100644 --- a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.jvm.kt @@ -34,6 +34,9 @@ actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { } @Composable -actual fun GallerySelectSingle(onImageUri: (SelectedMedia?) -> Unit) { +actual fun GallerySelectSingle( + imagesOnly: Boolean, + onImageUri: (SelectedMedia?) -> Unit, +) { LaunchedEffect(Unit) { onImageUri(null) } } diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.jvm.kt new file mode 100644 index 0000000000..3e746b9968 --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.jvm.kt @@ -0,0 +1,29 @@ +/* + * 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.ui.actions.uploads + +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia + +/** No frame decoder wired yet: the preview plays the video instead. */ +@Composable +actual fun rememberVideoThumbnail(media: SelectedMedia): ImageBitmap? = null diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt index 5cdb61e42b..874770bc15 100644 --- a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/SetDialogToEdgeToEdge.jvm.kt @@ -21,6 +21,17 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.runtime.Composable +import androidx.compose.ui.window.DialogProperties @Composable actual fun SetDialogToEdgeToEdge() {} + +actual fun edgeToEdgeDialogProperties( + dismissOnBackPress: Boolean, + dismissOnClickOutside: Boolean, +): DialogProperties = + DialogProperties( + usePlatformDefaultWidth = false, + dismissOnBackPress = dismissOnBackPress, + dismissOnClickOutside = dismissOnClickOutside, + ) diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModelRulesTest.kt b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityModelRulesTest.kt similarity index 98% rename from amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModelRulesTest.kt rename to commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityModelRulesTest.kt index 5183ecd75c..619d0b2d58 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/newCommunity/NewCommunityModelRulesTest.kt +++ b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/newCommunity/NewCommunityModelRulesTest.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.communities.newCommunity +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity import com.vitorpamplona.quartz.nip72ModCommunities.rules.tags.PubkeyRuleTag import org.junit.Assert.assertEquals From fdca5c70fffae5eb816dcab138132b61b6c28b20 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 18:45:17 +0000 Subject: [PATCH 16/20] refactor: share the file picker (SelectFromFiles) with an expect FileSelect Android keeps the OpenMultipleDocuments launcher; desktop and iOS report a cancel until they have a document picker. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../amethyst/ui/actions/EditPostView.kt | 2 +- .../nip22Comments/GenericCommentPostScreen.kt | 2 +- .../chats/privateDM/send/NewGroupDMScreen.kt | 2 +- .../nip23LongForm/LongFormPostScreen.kt | 2 +- .../nip99Classifieds/NewProductScreen.kt | 2 +- .../loggedIn/home/ShortNotePostScreen.kt | 2 +- .../publicMessages/NewPublicMessageScreen.kt | 2 +- .../ui/actions/uploads/FileSelect.android.kt | 71 +++++++++++++++++++ .../ui/actions/uploads/SelectFromFiles.kt | 45 +----------- .../ui/actions/uploads/FileSelect.ios.kt | 33 +++++++++ .../ui/actions/uploads/FileSelect.jvm.kt | 33 +++++++++ 11 files changed, 147 insertions(+), 49 deletions(-) create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/actions/uploads/SelectFromFiles.kt (69%) create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index 0cee50de61..e093d90651 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -79,6 +79,7 @@ import com.vitorpamplona.amethyst.commons.resources.lightning_invoice import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.BechLink import com.vitorpamplona.amethyst.commons.ui.components.OutlinedThinPaddingTextField @@ -102,7 +103,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier import com.vitorpamplona.amethyst.commons.ui.theme.subtleBorder import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.playback.composable.VideoView -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview import com.vitorpamplona.amethyst.ui.note.creators.invoice.InvoiceRequest import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index e27d53befe..484b7aa166 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -79,6 +79,7 @@ import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -110,7 +111,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPage import com.vitorpamplona.amethyst.commons.ui.theme.replyModifier import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.getActivity diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index 3622a98c27..c6e13b30be 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -88,6 +88,7 @@ import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -120,7 +121,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPa import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt index 48889ad2d5..838601fdc4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt @@ -103,6 +103,7 @@ import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler @@ -129,7 +130,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPa import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index 8c2f501739..b06be8a622 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -53,6 +53,7 @@ import com.vitorpamplona.amethyst.commons.resources.new_product import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -77,7 +78,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp import com.vitorpamplona.amethyst.commons.ui.theme.Size5dp import com.vitorpamplona.amethyst.commons.ui.theme.SuggestionListDefaultHeightPage import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.navigation.navs.Nav diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index b954506508..de50091951 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -110,6 +110,7 @@ import com.vitorpamplona.amethyst.commons.resources.what_s_on_your_mind import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -148,7 +149,6 @@ import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.mediaServers.FileServerSelectionRow import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS import com.vitorpamplona.amethyst.ui.actions.uploads.RecordVoiceButton -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.actions.uploads.UploadProgressIndicator diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index 248c8cb6a7..b3b9bce7ae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.commons.resources.public_message import com.vitorpamplona.amethyst.commons.resources.zapraiser import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.commons.ui.actions.uploads.SelectFromGallery import com.vitorpamplona.amethyst.commons.ui.components.PlatformBackHandler import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe @@ -90,7 +91,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.actions.UrlUserTagOutputTransformation -import com.vitorpamplona.amethyst.ui.actions.uploads.SelectFromFiles import com.vitorpamplona.amethyst.ui.actions.uploads.TakePictureButton import com.vitorpamplona.amethyst.ui.actions.uploads.TakeVideoButton import com.vitorpamplona.amethyst.ui.components.ThinPaddingTextField diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt new file mode 100644 index 0000000000..64f47649c6 --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt @@ -0,0 +1,71 @@ +/* + * 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.ui.actions.uploads + +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +import java.util.concurrent.atomic.AtomicBoolean + +@Composable +actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { + val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } + val resolver = LocalContext.current.contentResolver + + val launcher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenMultipleDocuments(), + onResult = { uris: List -> + onFilesSelected( + uris + .map { + SelectedMedia(it, resolver.getType(it)) + }.toImmutableList(), + ) + hasLaunched.set(false) + }, + ) + + @Composable + fun LaunchFilePicker() { + SideEffect { + if (!hasLaunched.getAndSet(true)) { + launcher.launch( + arrayOf( + "audio/*", + "application/pdf", + ), + ) + } + } + } + + LaunchFilePicker() +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromFiles.kt similarity index 69% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromFiles.kt index 425c25f84e..652b4963c9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/uploads/SelectFromFiles.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromFiles.kt @@ -18,22 +18,17 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.actions.uploads +package com.vitorpamplona.amethyst.commons.ui.actions.uploads -import android.net.Uri -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.height import androidx.compose.material3.IconButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols @@ -43,8 +38,6 @@ import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.commons.ui.stringRes import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.toImmutableList -import java.util.concurrent.atomic.AtomicBoolean @Composable fun SelectFromFiles( @@ -95,38 +88,6 @@ private fun FileSelectButton( } } +/** Opens the platform's document picker for audio and PDF files; reports an empty list on cancel. */ @Composable -fun FileSelect(onFilesSelected: (ImmutableList) -> Unit = {}) { - val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } - val resolver = LocalContext.current.contentResolver - - val launcher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.OpenMultipleDocuments(), - onResult = { uris: List -> - onFilesSelected( - uris - .map { - SelectedMedia(it, resolver.getType(it)) - }.toImmutableList(), - ) - hasLaunched.set(false) - }, - ) - - @Composable - fun LaunchFilePicker() { - SideEffect { - if (!hasLaunched.getAndSet(true)) { - launcher.launch( - arrayOf( - "audio/*", - "application/pdf", - ), - ) - } - } - } - - LaunchFilePicker() -} +expect fun FileSelect(onFilesSelected: (ImmutableList) -> Unit = {}) diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt new file mode 100644 index 0000000000..24c979e0ef --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt @@ -0,0 +1,33 @@ +/* + * 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.ui.actions.uploads + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +/** No document picker wired yet: reports a cancel. */ +@Composable +actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { + LaunchedEffect(Unit) { onFilesSelected(persistentListOf()) } +} diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt new file mode 100644 index 0000000000..24c979e0ef --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt @@ -0,0 +1,33 @@ +/* + * 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.ui.actions.uploads + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf + +/** No document picker wired yet: reports a cancel. */ +@Composable +actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { + LaunchedEffect(Unit) { onFilesSelected(persistentListOf()) } +} From 297abd11e1f00338159df6ed2996cbfbd17c39f1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 19:27:28 +0000 Subject: [PATCH 17/20] refactor: move the wallet and discovery tab screens to commonsUI - AppServices carries the Namecoin resolver, so the search row's NamecoinResolutionRow no longer reads Amethyst.instance. - ReusableZapButton is a NotePlatform slot; its ZapButtonConfig and ZapButtonCallbacks move to commonsUI. - rememberWalletAppLauncher (expect/actual) hands a BOLT-11 invoice to a wallet app: Android keeps its ACTION_VIEW intent and flags, desktop and iOS open the lightning: link through the UriHandler. - Moved (35 files): the wallet screen, DVM content discovery, favorite algo feeds, communities, articles, follow packs, live streams and products tabs, with their cards and the on-chain zap dialog. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../ui/components/ReusableZapButton.kt | 37 +++----------- .../amethyst/ui/navigation/AppNavigation.kt | 16 +++--- .../amethyst/ui/note/ReactionsRow.kt | 2 +- .../amethyst/ui/note/ZapCustomDialog.kt | 2 +- .../ui/note/platform/AndroidNotePlatform.kt | 12 +++++ .../ui/platform/AndroidAppServices.kt | 3 ++ .../loggedIn/BottomBarFeedPreloaders.kt | 10 ++-- .../chats/feed/ChatMessageActionSheet.kt | 2 +- .../loggedIn/discover/DiscoverScreen.kt | 1 + .../loggedIn/home/AlgoFeedStatusBanner.kt | 2 +- .../screen/loggedIn/nests/NestsFeedLoaded.kt | 2 +- .../notifications/donations/ZapTheDevsCard.kt | 4 +- .../profile/payment/SendPaymentScreen.kt | 4 +- .../ui/screen/loggedIn/search/SearchScreen.kt | 2 +- .../namecoin/NamecoinResolutionRowTest.kt | 2 + .../amethyst/commons/service/AppServices.kt | 7 ++- .../ui/wallet/WalletAppLauncher.android.kt | 47 ++++++++++++++++++ .../commons/ui/components/ZapButtonConfig.kt | 49 +++++++++++++++++++ .../namecoin/NamecoinResolutionRow.kt | 8 +-- .../commons/ui/note/platform/NotePlatform.kt | 12 +++++ .../ui/note/platform/NotePlatformCalls.kt | 11 +++++ .../loggedIn/articles/ArticlesFeedLoaded.kt | 4 +- .../loggedIn/articles/ArticlesScreen.kt | 4 +- .../loggedIn/articles/ArticlesTopBar.kt | 2 +- .../ArticlesFilterAssemblerSubscription.kt | 2 +- .../communities/list/CommunitiesScreen.kt | 6 +-- .../communities/list/CommunitiesTopBar.kt | 2 +- ...munitiesListFilterAssemblerSubscription.kt | 2 +- .../loggedIn/discover/ChannelCardCompose.kt | 12 ++--- .../discover/nip23LongForm/LongFormCard.kt | 2 +- .../nip53LiveActivities/LiveActivityCard.kt | 3 +- .../nip72Communities/CommunityCard.kt | 3 +- .../loggedIn/discover/nip90DVMs/DVMCard.kt | 6 +-- .../nip99Classifieds/ClassifiedsThumb.kt | 2 +- .../dvms/DvmContentDiscoveryScreen.kt | 25 +++++----- .../ui/screen/loggedIn/dvms/DvmTopBar.kt | 2 +- .../loggedIn/dvms/FavoriteAlgoFeedToggle.kt | 2 +- .../dal/DvmContentDiscoveryFeedViewModel.kt | 9 +++- .../dal/DvmContentDiscoveryResponseFilter.kt | 2 +- .../favorites/FavoriteAlgoFeedsListScreen.kt | 4 +- .../followPacks/list/FollowPacksFeedLoaded.kt | 4 +- .../followPacks/list/FollowPacksScreen.kt | 4 +- .../followPacks/list/FollowPacksTopBar.kt | 2 +- .../FollowPacksFilterAssemblerSubscription.kt | 2 +- .../livestreams/LiveStreamsFeedLoaded.kt | 4 +- .../loggedIn/livestreams/LiveStreamsScreen.kt | 4 +- .../loggedIn/livestreams/LiveStreamsTopBar.kt | 2 +- .../LiveStreamsFilterAssemblerSubscription.kt | 2 +- .../loggedIn/products/ProductsFeedLoaded.kt | 4 +- .../loggedIn/products/ProductsScreen.kt | 4 +- .../loggedIn/products/ProductsTopBar.kt | 2 +- .../ProductsFilterAssemblerSubscription.kt | 2 +- .../screen/loggedIn/wallet/OnchainSection.kt | 2 +- .../loggedIn/wallet/OnchainZapSendDialog.kt | 9 ++-- .../ui/screen/loggedIn/wallet/WalletScreen.kt | 8 +-- .../commons/ui/wallet/WalletAppLauncher.kt | 40 +++++++++++++++ .../ui/wallet/WalletAppLauncher.ios.kt | 43 ++++++++++++++++ .../ui/wallet/WalletAppLauncher.jvm.kt | 43 ++++++++++++++++ 58 files changed, 381 insertions(+), 129 deletions(-) create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.android.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/components/namecoin/NamecoinResolutionRow.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/articles/ArticlesFeedLoaded.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/articles/ArticlesScreen.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/articles/ArticlesTopBar.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/communities/list/CommunitiesScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/communities/list/CommunitiesTopBar.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/discover/ChannelCardCompose.kt (93%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/discover/nip23LongForm/LongFormCard.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/dvms/DvmTopBar.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/dvms/FavoriteAlgoFeedToggle.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryFeedViewModel.kt (83%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryResponseFilter.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/followPacks/list/FollowPacksFeedLoaded.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/followPacks/list/FollowPacksScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/followPacks/list/FollowPacksTopBar.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/livestreams/LiveStreamsFeedLoaded.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/livestreams/LiveStreamsScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/livestreams/LiveStreamsTopBar.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/products/ProductsFeedLoaded.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/products/ProductsScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/products/ProductsTopBar.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/products/datasource/ProductsFilterAssemblerSubscription.kt (96%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/OnchainSection.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/wallet/WalletScreen.kt (98%) create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.ios.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.jvm.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ReusableZapButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ReusableZapButton.kt index 24239668ff..7a2098bbe2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ReusableZapButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/ReusableZapButton.kt @@ -39,9 +39,7 @@ import androidx.compose.runtime.rememberCoroutineScope 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.unit.Dp import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.User @@ -58,21 +56,20 @@ import com.vitorpamplona.amethyst.commons.resources.no_zap_amount_setup_long_pre import com.vitorpamplona.amethyst.commons.resources.thank_you import com.vitorpamplona.amethyst.commons.service.ZapPaymentHandler import com.vitorpamplona.amethyst.commons.ui.components.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonCallbacks +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonConfig import com.vitorpamplona.amethyst.commons.ui.components.toasts.multiline.UserBasedErrorMessage import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon +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 import com.vitorpamplona.amethyst.commons.ui.theme.ModifierWidth3dp -import com.vitorpamplona.amethyst.commons.ui.theme.Size14Modifier -import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier -import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.ui.note.ObserveZapIcon import com.vitorpamplona.amethyst.ui.note.ZapAmountChoicePopup import com.vitorpamplona.amethyst.ui.note.payViaIntent -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.collections.immutable.ImmutableList @@ -83,35 +80,14 @@ import kotlinx.coroutines.launch import kotlin.uuid.ExperimentalUuidApi import kotlin.uuid.Uuid -/** - * Configuration for zap button behavior and appearance - */ -data class ZapButtonConfig( - val grayTint: Color = Color.Gray, - val iconSize: Dp = Size35dp, - val iconSizeModifier: Modifier = Size20Modifier, - val animationModifier: Modifier = Size14Modifier, - val showUserFinderSubscription: Boolean = false, - val zapAmountChoices: List? = null, - val thankYouText: String? = null, - val buttonText: String? = null, -) - -/** - * Callbacks for zap button events - */ -data class ZapButtonCallbacks( - val onZapComplete: ((Boolean) -> Unit)? = null, -) - @OptIn(ExperimentalUuidApi::class) @Composable fun ReusableZapButton( baseNote: Note, accountViewModel: AccountViewModel, nav: INav, - config: ZapButtonConfig = ZapButtonConfig(), - callbacks: ZapButtonCallbacks = ZapButtonCallbacks(), + config: ZapButtonConfig, + callbacks: ZapButtonCallbacks, ) { val noWalletFoundStr = stringRes(Res.string.no_wallet_found) var wantsToZap by remember { mutableStateOf?>(null) } @@ -297,8 +273,7 @@ fun ReusableZapButton( val displayText = when { hasZapped -> config.thankYouText ?: stringRes(id = Res.string.thank_you) - config.buttonText != null -> config.buttonText - else -> stringRes(id = Res.string.donate_now) + else -> config.buttonText ?: stringRes(id = Res.string.donate_now) } Text(text = displayText) 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 9a4bea97d8..3ab4965e21 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 @@ -65,6 +65,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.LocalTabResel import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.TabReselectCoordinator import com.vitorpamplona.amethyst.commons.ui.navigation.findQueryParameterValue import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.apps.recommendations.ProfileAppRecommendationsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles.ArticlesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.backups.BackupConflictReviewScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.BadgesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.award.AwardBadgeScreen @@ -103,10 +104,13 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.relayGroup.RelayGroupThreadsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.CommunityScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list.CommunitiesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity.EditCommunityScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.newCommunity.NewCommunityScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.contactList.ContactListUsersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.drafts.DraftListScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.DvmContentDiscoveryScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.favorites.FavoriteAlgoFeedsListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.browse.BrowseEmojiSetsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.display.EmojiPackScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.list.ListOfEmojiPacksScreen @@ -114,6 +118,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.list.met import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.EmojiPackSelectionScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.membershipManagement.MyEmojiListScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.FavoriteAppsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list.FollowPacksScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.GeocacheHuntScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.NewGeocacheHuntScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.GeoHashScreen @@ -127,6 +132,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.lists import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.display.packs.FollowPackScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.list.ListOfPeopleListsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.livestreams.LiveStreamsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.MusicPlaylistsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.MusicTracksScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.NewMusicPlaylistScreen @@ -141,6 +147,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring. 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.publicChats.PublicChatsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.qrcode.ShowQRScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.redirect.LoadRedirectScreen @@ -183,6 +190,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.ReloadMintSc import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TopUpMintScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletDetailScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletReceiveScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletSendScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletTransactionsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.wizard.CashuWalletWizardScreen @@ -218,7 +226,6 @@ import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsImageScreen import com.vitorpamplona.amethyst.ui.note.share.ShareNoteAsQrScreen import com.vitorpamplona.amethyst.ui.screen.AccountSessionManager import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountSwitcherAndLeftDrawerLayout -import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.ArticlesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.display.BookmarkGroupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.list.metadata.BookmarkGroupMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.browser.WebAppScreen @@ -259,12 +266,9 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.MessagesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.share.ShareToDMScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessGameScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.chess.ChessLobbyScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.list.CommunitiesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.LongFormPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.NewProductScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.DvmContentDiscoveryScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.favorites.FavoriteAlgoFeedsListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabAccountWatcher import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabLayer import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabPreloader @@ -272,7 +276,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.EmbeddedTabThemeWatch import com.vitorpamplona.amethyst.ui.screen.loggedIn.embed.FavoriteAppManifestPreloader import com.vitorpamplona.amethyst.ui.screen.loggedIn.favorites.NostrAppScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.FollowPackFeedScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.list.FollowPacksScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.GeocachesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.create.NewGeocacheScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail.GeocacheDetailScreen @@ -292,7 +295,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.nip75Goals.NewGoalScre import com.vitorpamplona.amethyst.ui.screen.loggedIn.keyBackup.AccountBackupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.FollowPackMetadataScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleListMetadataScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.LiveStreamsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.LongsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.AddToMusicPlaylistSheet import com.vitorpamplona.amethyst.ui.screen.loggedIn.music.NewMusicTrackScreen @@ -308,7 +310,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.EditPodc import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastEpisodeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastTrailerScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.PollPostScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.ProductsScreen 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 @@ -337,7 +338,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.url.UrlScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.VideoScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.hls.NewHlsVideoScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.WalletScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.webBookmarks.WebBookmarksScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.NewWorkoutScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.workouts.fitness.MyFitnessScreen 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 7bda67d532..88beb7be95 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 @@ -195,6 +195,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.ZappedIcon import com.vitorpamplona.amethyst.commons.ui.note.elements.ShareOptionsBottomSheet 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.wallet.OnchainZapSendDialog import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.navigateToReloadMint import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange @@ -243,7 +244,6 @@ import com.vitorpamplona.amethyst.ui.actions.uploads.MAX_VOICE_RECORD_SECONDS 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.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog 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 f1261acfd5..f717e04ab5 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 @@ -109,6 +109,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.BaseUserPicture import com.vitorpamplona.amethyst.commons.ui.note.DisplayBlankAuthor import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.commons.ui.note.buttons.CloseButton +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.OnchainZapSendDialog import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.commons.ui.theme.DoubleHorzSpacer @@ -119,7 +120,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.ZeroPadding import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.util.showAmount import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent import kotlinx.collections.immutable.ImmutableList diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/platform/AndroidNotePlatform.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/platform/AndroidNotePlatform.kt index ff1a900619..f887f9ff58 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/platform/AndroidNotePlatform.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/platform/AndroidNotePlatform.kt @@ -34,6 +34,8 @@ import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonCallbacks +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonConfig import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.platform.NotePlatform import com.vitorpamplona.amethyst.commons.ui.note.types.EditState @@ -50,6 +52,7 @@ import com.vitorpamplona.amethyst.service.playback.composable.VideoView as AppVi import com.vitorpamplona.amethyst.ui.actions.EditPostView as AppEditPostView import com.vitorpamplona.amethyst.ui.components.GifVideoView as AppGifVideoView import com.vitorpamplona.amethyst.ui.components.LoadUrlPreview as AppLoadUrlPreview +import com.vitorpamplona.amethyst.ui.components.ReusableZapButton as AppReusableZapButton import com.vitorpamplona.amethyst.ui.components.ZoomableContentView as AppZoomableContentView import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog as AppZoomableImageDialog import com.vitorpamplona.amethyst.ui.components.rememberUrlPreviewState as AppRememberUrlPreviewState @@ -584,6 +587,15 @@ object AndroidNotePlatform : NotePlatform { nav = nav, ) + @Composable + override fun ReusableZapButton( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, + config: ZapButtonConfig, + callbacks: ZapButtonCallbacks, + ) = AppReusableZapButton(baseNote, accountViewModel, nav, config, callbacks) + @Composable override fun EditPostView( onClose: () -> Unit, 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 8538e98df4..b31a64e385 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 @@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppsRegistry import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionLedger import com.vitorpamplona.amethyst.commons.service.AppServices import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver /** * [AppServices] over the main process's app modules. Every member is a getter, so installing @@ -46,4 +47,6 @@ object AndroidAppServices : AppServices { override val signerPermissionStore: NostrSignerPermissionStore get() = Amethyst.instance.signerPermissionStore override val torSettings: TorSettingsFlow get() = Amethyst.instance.torPrefs.value + + override val namecoinResolver: NamecoinNameResolver get() = Amethyst.instance.namecoinResolver } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt index bbf874cb41..557a03c373 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/BottomBarFeedPreloaders.kt @@ -26,32 +26,32 @@ import androidx.compose.runtime.key import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.model.navigation.BottomBarEntry import com.vitorpamplona.amethyst.commons.model.navigation.NavBarItem +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.badges.datasource.BadgesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.chats.publicChannels.concord.datasource.ConcordChannelSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list.datasource.CommunitiesListFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.emojipacks.browse.datasource.BrowseEmojiSetsFilterAssemblerSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list.datasource.FollowPacksFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.datasource.GeocachesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.gitRepositories.datasource.GitRepositoriesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.highlights.datasource.HighlightsFilterAssemblerSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.livestreams.datasource.LiveStreamsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.datasource.MusicTracksFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.datasource.PodcastEpisodesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.datasource.PodcastsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.datasource.PollsFilterAssemblerSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.products.datasource.ProductsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.publicChats.datasource.PublicChatsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.softwareapps.datasource.SoftwareAppsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.workouts.datasource.WorkoutsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.calendars.datasource.CalendarsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.datasource.ChatroomListFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.list.datasource.CommunitiesListFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.DiscoveryFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.list.datasource.FollowPacksFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.datasource.LiveStreamsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.longs.datasource.LongsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.datasource.NestsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.datasource.PicturesFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.datasource.ProductsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.shorts.datasource.ShortsFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.VideoFilterAssemblerSubscription diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt index 889d177e43..33c9ea23e0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/feed/ChatMessageActionSheet.kt @@ -93,6 +93,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.elements.ShareOptionsBottomShe import com.vitorpamplona.amethyst.commons.ui.note.elements.noteActionSections import com.vitorpamplona.amethyst.commons.ui.note.elements.observeBookmarksFollowsAndAccount import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.report.ReportNoteDialog +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 import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness @@ -106,7 +107,6 @@ import com.vitorpamplona.amethyst.ui.actions.EditPostView import com.vitorpamplona.amethyst.ui.note.ZapAmountChoiceGrid import com.vitorpamplona.amethyst.ui.note.observeZapRailCapability import com.vitorpamplona.amethyst.ui.note.payViaIntentOrManualSplit -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.OnchainZapSendDialog import com.vitorpamplona.quartz.buzz.stream.StreamMessageV2Event import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nipC7Chats.ChatEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt index c32b75daab..b8727ee1e4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt @@ -79,6 +79,7 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded 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.discover.ChannelCardCompose import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.FeedPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt index 3d2372b282..5ba2457880 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt @@ -54,12 +54,12 @@ import com.vitorpamplona.amethyst.commons.resources.dvm_offline_banner import com.vitorpamplona.amethyst.commons.ui.components.LoadNote import com.vitorpamplona.amethyst.commons.ui.components.LoadingAnimation import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.DvmPaymentActions import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.rememberDvmHeartbeatFresh import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.DvmPaymentActions import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt index b85b8f9380..68b0c4b973 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/NestsFeedLoaded.kt @@ -94,6 +94,7 @@ import com.vitorpamplona.amethyst.commons.ui.note.elements.MoreOptionsButton import com.vitorpamplona.amethyst.commons.ui.note.platform.LikeReaction import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction import com.vitorpamplona.amethyst.commons.ui.note.timeAgoNoDot +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip53LiveActivities.LoadParticipants import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DividerThickness import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer @@ -107,7 +108,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.LoadParticipants import com.vitorpamplona.amethyst.ui.screen.loggedIn.nests.room.activity.NestActivity import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt index b0484b9cce..8177855b07 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt @@ -67,12 +67,14 @@ import com.vitorpamplona.amethyst.commons.resources.version_name import com.vitorpamplona.amethyst.commons.resources.zap_the_devs_description import com.vitorpamplona.amethyst.commons.resources.zap_the_devs_title import com.vitorpamplona.amethyst.commons.ui.components.LoadNote +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonConfig import com.vitorpamplona.amethyst.commons.ui.components.appendLink 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.routes.routeFor import com.vitorpamplona.amethyst.commons.ui.note.ZapIcon import com.vitorpamplona.amethyst.commons.ui.note.creators.zapsplits.DisplayZapSplits +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReusableZapButton import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.commons.ui.theme.Size10dp @@ -80,8 +82,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel -import com.vitorpamplona.amethyst.ui.components.ReusableZapButton -import com.vitorpamplona.amethyst.ui.components.ZapButtonConfig import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt index 045e37ad70..f30f5a1bbe 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/payment/SendPaymentScreen.kt @@ -105,13 +105,13 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackBu import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture 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.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.amethyst.ui.screen.loggedIn.wallet.FeeTier -import com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet.rateFor import com.vitorpamplona.quartz.experimental.clink.common.SatRange import com.vitorpamplona.quartz.experimental.clink.offers.OfferErrorCode import com.vitorpamplona.quartz.experimental.clink.pointers.ClinkPointerParser diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt index 77cd6269d3..2e0c06fa66 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt @@ -117,6 +117,7 @@ import com.vitorpamplona.amethyst.commons.search.QuerySerializer import com.vitorpamplona.amethyst.commons.search.SearchScope import com.vitorpamplona.amethyst.commons.search.SearchSortOrder import com.vitorpamplona.amethyst.commons.search.SearchSource +import com.vitorpamplona.amethyst.commons.ui.components.namecoin.NamecoinResolutionRow import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding @@ -146,7 +147,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.placeholderText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.amethyst.service.location.CachedReversedGeoLocations import com.vitorpamplona.amethyst.service.relayClient.searchCommand.TextSearchDataSourceSubscription -import com.vitorpamplona.amethyst.ui.components.namecoin.NamecoinResolutionRow import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelName import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl import kotlinx.collections.immutable.toImmutableList diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt index 6436ea254f..5e7e0f4a10 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt @@ -21,6 +21,8 @@ package com.vitorpamplona.amethyst.ui.components.namecoin import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.namecoin.NamecoinResolveState +import com.vitorpamplona.amethyst.commons.ui.components.namecoin.looksLikeNamecoinIdentifier +import com.vitorpamplona.amethyst.commons.ui.components.namecoin.mapOutcomeToResolveState import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNostrResult import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinResolveOutcome import org.junit.Assert.assertEquals 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 692b4cb9fe..37ca48c1a4 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 @@ -26,10 +26,12 @@ import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermi import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppsRegistry import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionLedger import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver /** * App-wide stores that outlive any one account and that screens read directly: the favorites - * row, the browser's history and favicons, what connected apps may do, and the Tor settings. + * row, the browser's history and favicons, what connected apps may do, the Tor settings and the + * Namecoin resolver. * Account-scoped services reach screens through the account's ViewModel instead. * * A front end implements it over its own modules; shared UI reads it through `LocalAppServices`. @@ -48,4 +50,7 @@ interface AppServices { val signerPermissionStore: NostrSignerPermissionStore val torSettings: TorSettingsFlow + + /** Resolves `.bit` names and `d/`/`id/` identifiers over the configured ElectrumX servers. */ + val namecoinResolver: NamecoinNameResolver } diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.android.kt new file mode 100644 index 0000000000..2977a00f85 --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.android.kt @@ -0,0 +1,47 @@ +/* + * 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.ui.wallet + +import android.content.Intent +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +import androidx.core.net.toUri +import kotlin.coroutines.cancellation.CancellationException + +@Composable +actual fun rememberWalletAppLauncher(): WalletAppLauncher { + val context = LocalContext.current + return remember(context) { + WalletAppLauncher { invoice, noWalletFound, onPaid, onError -> + 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 + onError(noWalletFound) + } + } + } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt new file mode 100644 index 0000000000..a1d2260b25 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt @@ -0,0 +1,49 @@ +/* + * 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.ui.components + +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import com.vitorpamplona.amethyst.commons.ui.theme.Size14Modifier +import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier +import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp + +/** + * Configuration for zap button behavior and appearance + */ +data class ZapButtonConfig( + val grayTint: Color = Color.Gray, + val iconSize: Dp = Size35dp, + val iconSizeModifier: Modifier = Size20Modifier, + val animationModifier: Modifier = Size14Modifier, + val showUserFinderSubscription: Boolean = false, + val zapAmountChoices: List? = null, + val thankYouText: String? = null, + val buttonText: String? = null, +) + +/** + * Callbacks for zap button events + */ +data class ZapButtonCallbacks( + val onZapComplete: ((Boolean) -> Unit)? = null, +) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/namecoin/NamecoinResolutionRow.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/namecoin/NamecoinResolutionRow.kt index c49bffebb1..766c0e95eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/namecoin/NamecoinResolutionRow.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.components.namecoin +package com.vitorpamplona.amethyst.commons.ui.components.namecoin import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -45,16 +45,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.namecoin.NamecoinResolveState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.note.UserPicture +import com.vitorpamplona.amethyst.commons.ui.platform.LocalAppServices import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinResolveOutcome import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.delay import kotlinx.coroutines.withContext @@ -162,6 +163,7 @@ fun NamecoinResolutionRow( if (!looksLikeNamecoinIdentifier(trimmed)) return var state by remember { mutableStateOf(null) } + val resolver = LocalAppServices.current.namecoinResolver LaunchedEffect(trimmed) { // Match UserSuggestionState's 300 ms debounce so we don't fire a @@ -171,7 +173,7 @@ fun NamecoinResolutionRow( val outcome = withContext(Dispatchers.IO) { runCatching { - Amethyst.instance.namecoinResolver.resolveDetailed(trimmed) + resolver.resolveDetailed(trimmed) }.getOrElse { NamecoinResolveOutcome.ServersUnreachable( it.message ?: it::class.simpleName ?: "Lookup error", diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatform.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatform.kt index ab6e88a774..367dd82ee0 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatform.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatform.kt @@ -36,6 +36,8 @@ import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonCallbacks +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonConfig import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.CalendarRsvpCard import com.vitorpamplona.amethyst.commons.ui.note.types.EditState @@ -398,6 +400,16 @@ interface NotePlatform { nav: INav, ) {} + /** A labelled zap button for [baseNote]'s author, outside a reactions row (DVMs, donations). */ + @Composable + fun ReusableZapButton( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, + config: ZapButtonConfig, + callbacks: ZapButtonCallbacks, + ) {} + /** The editor for a new version of [edit], shown while [versionLookingAt] is on screen. */ @Composable fun EditPostView( diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatformCalls.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatformCalls.kt index 00b25ef085..30f946780d 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatformCalls.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/platform/NotePlatformCalls.kt @@ -35,6 +35,8 @@ import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.richtext.BaseMediaContent import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonCallbacks +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonConfig import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.types.EditState import com.vitorpamplona.amethyst.commons.ui.theme.Font14SP @@ -534,6 +536,15 @@ fun ZapReaction( nav = nav, ) +@Composable +fun ReusableZapButton( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, + config: ZapButtonConfig = ZapButtonConfig(), + callbacks: ZapButtonCallbacks = ZapButtonCallbacks(), +) = LocalNotePlatform.current.ReusableZapButton(baseNote, accountViewModel, nav, config, callbacks) + @Composable fun EditPostView( onClose: () -> Unit, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesFeedLoaded.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesFeedLoaded.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesFeedLoaded.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesFeedLoaded.kt index 7ead0106ea..d0e59db432 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesFeedLoaded.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesFeedLoaded.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.articles +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -33,10 +33,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.ChannelCardCompose 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.discover.ChannelCardCompose import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesScreen.kt index 45c41fe5e5..52bdd4c631 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesScreen.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.articles +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -36,8 +36,8 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded 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.articles.datasource.ArticlesFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.articles.datasource.ArticlesFilterAssemblerSubscription @Composable fun ArticlesScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesTopBar.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesTopBar.kt index 8d1ef3f756..b9a36fd5ba 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/ArticlesTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/ArticlesTopBar.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.articles +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssemblerSubscription.kt index 9ca1720f0a..2d4e86b988 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/articles/datasource/ArticlesFilterAssemblerSubscription.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.articles.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.articles.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/CommunitiesScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/CommunitiesScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/CommunitiesScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/CommunitiesScreen.kt index 56636c4501..c0ce4a941c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/CommunitiesScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/CommunitiesScreen.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.communities.list +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Row @@ -49,11 +49,11 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded 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.communities.list.datasource.CommunitiesListFilterAssemblerSubscription +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.ChannelCardCompose 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.communities.list.datasource.CommunitiesListFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.ChannelCardCompose import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/CommunitiesTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/CommunitiesTopBar.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/CommunitiesTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/CommunitiesTopBar.kt index 3dfeaa1535..37c093311a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/CommunitiesTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/CommunitiesTopBar.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.communities.list +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssemblerSubscription.kt index ecd20288d1..d60054fc82 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/communities/list/datasource/CommunitiesListFilterAssemblerSubscription.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.communities.list.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.communities.list.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/ChannelCardCompose.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/ChannelCardCompose.kt index 5d6d783791..53707b4c73 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/ChannelCardCompose.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/ChannelCardCompose.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.discover +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable @@ -33,16 +33,16 @@ import com.vitorpamplona.amethyst.commons.ui.note.ClickableNote import com.vitorpamplona.amethyst.commons.ui.note.LongPressToQuickAction import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.commons.ui.note.calculateBackgroundColor +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip23LongForm.RenderLongFormThumb import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip28Chats.RenderPublicChatChannelThumb import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip51FollowSets.RenderFollowSetThumb +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip53LiveActivities.RenderLiveActivityThumb +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip72Communities.RenderCommunitiesThumb +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip90DVMs.RenderContentDVMThumb +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip99Classifieds.RenderClassifiedsThumb import com.vitorpamplona.amethyst.commons.ui.theme.HalfPadding import com.vitorpamplona.amethyst.commons.ui.theme.StdPadding import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip23LongForm.RenderLongFormThumb -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip53LiveActivities.RenderLiveActivityThumb -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip72Communities.RenderCommunitiesThumb -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.RenderContentDVMThumb -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds.RenderClassifiedsThumb import com.vitorpamplona.quartz.nip23LongContent.LongFormContentEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormCard.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip23LongForm/LongFormCard.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormCard.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip23LongForm/LongFormCard.kt index 5f8625bc94..4b3d92beef 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormCard.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip23LongForm/LongFormCard.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.discover.nip23LongForm +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip23LongForm import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt index 24ac29e322..ae2db40ec4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip53LiveActivities/LiveActivityCard.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.discover.nip53LiveActivities +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip53LiveActivities import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -78,6 +78,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.launch @Immutable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt index 6bca972c92..8794710ba9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip72Communities/CommunityCard.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.discover.nip72Communities +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip72Communities import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row @@ -76,6 +76,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.launch @Immutable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt index 421ee5f46b..d51348cd31 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip90DVMs/DVMCard.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.discover.nip90DVMs +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip90DVMs import androidx.compose.foundation.border import androidx.compose.foundation.layout.Box @@ -48,6 +48,8 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.elements.BannerImage import com.vitorpamplona.amethyst.commons.ui.note.platform.LikeReaction import com.vitorpamplona.amethyst.commons.ui.note.platform.ZapReaction +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.FavoriteAlgoFeedToggle +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.observeAppDefinition import com.vitorpamplona.amethyst.commons.ui.theme.HalfTopPadding import com.vitorpamplona.amethyst.commons.ui.theme.RowColSpacing5dp import com.vitorpamplona.amethyst.commons.ui.theme.SimpleImageBorder @@ -56,8 +58,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.ui.theme.nip05 import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.FavoriteAlgoFeedToggle -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.observeAppDefinition @Immutable data class DVMCard( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt index d39c806415..3ae43f1201 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/discover/nip99Classifieds/ClassifiedsThumb.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.discover.nip99Classifieds +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip99Classifieds import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt index c33d41a4f6..ceb76c6f5d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.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.dvms +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -41,14 +41,12 @@ import androidx.compose.ui.Alignment.Companion.BottomStart import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.model.Note import com.vitorpamplona.amethyst.commons.model.User import com.vitorpamplona.amethyst.commons.model.payments.PaymentSource @@ -69,6 +67,8 @@ import com.vitorpamplona.amethyst.commons.service.nwc.nwcFailureDetail import com.vitorpamplona.amethyst.commons.service.nwc.nwcTimeoutMessage import com.vitorpamplona.amethyst.commons.ui.components.LoadNote import com.vitorpamplona.amethyst.commons.ui.components.MyAsyncImage +import com.vitorpamplona.amethyst.commons.ui.components.ZapButtonConfig +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.FeedEmpty import com.vitorpamplona.amethyst.commons.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold @@ -76,26 +76,26 @@ import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.commons.ui.note.elements.BannerImage +import com.vitorpamplona.amethyst.commons.ui.note.platform.ReusableZapButton import com.vitorpamplona.amethyst.commons.ui.screen.RenderFeedState import com.vitorpamplona.amethyst.commons.ui.screen.SaveableFeedState +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.nip90DVMs.DVMCard import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.DvmOfflineBanner +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.dal.DvmContentDiscoveryFeedViewModel import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.rememberDvmHeartbeatFresh import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.SimpleImage75Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp +import com.vitorpamplona.amethyst.commons.ui.wallet.rememberWalletAppLauncher import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.components.ReusableZapButton -import com.vitorpamplona.amethyst.ui.components.ZapButtonConfig -import com.vitorpamplona.amethyst.ui.note.payViaIntent -import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip90DVMs.DVMCard -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.dal.DvmContentDiscoveryFeedViewModel import com.vitorpamplona.quartz.lightning.LnInvoiceUtil import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppMetadata import com.vitorpamplona.quartz.nip90Dvms.contentDiscoveryResponse.DvmContentDiscoveryResponseEvent import com.vitorpamplona.quartz.nip90Dvms.status.DvmStatusEvent +import com.vitorpamplona.quartz.utils.toLongValue import kotlinx.coroutines.launch @Composable @@ -266,7 +266,7 @@ fun PrepareViewContentDiscoveryModels( nav: INav, ) { val resultFeedViewModel: DvmContentDiscoveryFeedViewModel = - viewModel( + rememberViewModel( key = "NostrNIP90ContentDiscoveryFeedViewModel${dvm.pubkeyHex}$dvmRequestId", factory = DvmContentDiscoveryFeedViewModel.Factory(accountViewModel.account, dvmKey = dvm.pubkeyHex, requestId = dvmRequestId), ) @@ -398,7 +398,7 @@ fun DvmPaymentActions( val nwcPaymentRequest = stringRes(id = Res.string.nwc_payment_request) if (invoice != null) { - val context = LocalContext.current + val walletApp = rememberWalletAppLauncher() Button(onClick = { when (val source = accountViewModel.account.settings.defaultPaymentSource()) { is PaymentSource.ClinkDebit -> { @@ -436,9 +436,8 @@ fun DvmPaymentActions( } null -> { - payViaIntent( + walletApp.payInvoice( invoice, - context, noWalletFoundStr, onPaid = { onStatusUpdate(thankYou) @@ -452,7 +451,7 @@ fun DvmPaymentActions( }) { val amountInInvoice = try { - LnInvoiceUtil.getAmountInSats(invoice).toLong() + LnInvoiceUtil.getAmountInSats(invoice).toLongValue() } catch (_: Exception) { null } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmTopBar.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmTopBar.kt index 3dc424bd2c..d7640bd075 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmTopBar.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.dvms +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms import androidx.compose.foundation.layout.Spacer import androidx.compose.material3.IconButton diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/FavoriteAlgoFeedToggle.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/FavoriteAlgoFeedToggle.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/FavoriteAlgoFeedToggle.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/FavoriteAlgoFeedToggle.kt index 226f2b0240..4f89453c1d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/FavoriteAlgoFeedToggle.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/FavoriteAlgoFeedToggle.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.dvms +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryFeedViewModel.kt similarity index 83% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryFeedViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryFeedViewModel.kt index d27acc98da..89355d0204 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryFeedViewModel.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.dvms.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.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.ui.screen.AndroidFeedViewModel +import kotlin.reflect.KClass @Stable class DvmContentDiscoveryFeedViewModel( @@ -38,6 +40,9 @@ class DvmContentDiscoveryFeedViewModel( val requestId: String, ) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T = DvmContentDiscoveryFeedViewModel(account, dvmKey, requestId) as T + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = DvmContentDiscoveryFeedViewModel(account, dvmKey, requestId) as T } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryResponseFilter.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryResponseFilter.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryResponseFilter.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryResponseFilter.kt index c438c3ea8c..b11353ce50 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryResponseFilter.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/dal/DvmContentDiscoveryResponseFilter.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.dvms.dal +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.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/dvms/favorites/FavoriteAlgoFeedsListScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt index 6a6b8a143e..19a633532f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.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.dvms.favorites +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.favorites import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable @@ -84,6 +84,7 @@ 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.elements.BannerImage import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.observeAppDefinition import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.dvms.rememberDvmHeartbeatFresh import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.ui.theme.DoubleHorzSpacer @@ -92,7 +93,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.SimpleImage35Modifier import com.vitorpamplona.amethyst.commons.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.commons.ui.theme.grayText import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.dvms.observeAppDefinition import kotlinx.coroutines.launch private const val STAR_INLINE_ID = "star" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksFeedLoaded.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksFeedLoaded.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksFeedLoaded.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksFeedLoaded.kt index 13090f0708..429826b943 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksFeedLoaded.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksFeedLoaded.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.followPacks.list +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Row @@ -34,10 +34,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.ChannelCardCompose 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.discover.ChannelCardCompose import com.vitorpamplona.quartz.nip51Lists.starterPack.StarterPackEvent @OptIn(ExperimentalFoundationApi::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksScreen.kt index 0043f6c6a7..88f4af5c29 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksScreen.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.followPacks.list +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -34,8 +34,8 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold 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.followPacks.list.datasource.FollowPacksFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.list.datasource.FollowPacksFilterAssemblerSubscription @Composable fun FollowPacksScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksTopBar.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksTopBar.kt index d50031d6a7..e7e0f59b0f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/FollowPacksTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/FollowPacksTopBar.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.followPacks.list +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssemblerSubscription.kt index 882b723143..910cb30a9f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/followPacks/list/datasource/FollowPacksFilterAssemblerSubscription.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.followPacks.list.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsFeedLoaded.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsFeedLoaded.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsFeedLoaded.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsFeedLoaded.kt index 3d9f4368ff..489b03139c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsFeedLoaded.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsFeedLoaded.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.livestreams +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.livestreams import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Row @@ -34,10 +34,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.ChannelCardCompose 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.discover.ChannelCardCompose import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent @OptIn(ExperimentalFoundationApi::class) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsScreen.kt index 370f855c61..b811e5ed08 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsScreen.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.livestreams +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.livestreams import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -34,8 +34,8 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold 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.livestreams.datasource.LiveStreamsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.livestreams.datasource.LiveStreamsFilterAssemblerSubscription @Composable fun LiveStreamsScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsTopBar.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsTopBar.kt index c0254eb04b..fec93abbaa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/LiveStreamsTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/LiveStreamsTopBar.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.livestreams +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.livestreams import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssemblerSubscription.kt index 0b8870df4c..67a82ceba1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/livestreams/datasource/LiveStreamsFilterAssemblerSubscription.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.livestreams.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.livestreams.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsFeedLoaded.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsFeedLoaded.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsFeedLoaded.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsFeedLoaded.kt index c8c8f0d7d8..43e921e9a3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsFeedLoaded.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsFeedLoaded.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.products +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.products import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi @@ -42,10 +42,10 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.commons.ui.feeds.PrefetchLoadedFeedMedia import com.vitorpamplona.amethyst.commons.ui.layouts.rememberFeedContentPadding import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.discover.ChannelCardCompose 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.discover.ChannelCardCompose import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsScreen.kt index 83a312fd5f..00d233df05 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsScreen.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.products +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.products import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -35,8 +35,8 @@ import com.vitorpamplona.amethyst.commons.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.commons.ui.navigation.bottombars.FabBottomBarPadded 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.products.datasource.ProductsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.products.datasource.ProductsFilterAssemblerSubscription @Composable fun ProductsScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsTopBar.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsTopBar.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsTopBar.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsTopBar.kt index 6e7ff0a6d9..d377522fec 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/ProductsTopBar.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/ProductsTopBar.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.products +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.products import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/datasource/ProductsFilterAssemblerSubscription.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/datasource/ProductsFilterAssemblerSubscription.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/datasource/ProductsFilterAssemblerSubscription.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/datasource/ProductsFilterAssemblerSubscription.kt index f5c0c2b7f5..929867fef4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/products/datasource/ProductsFilterAssemblerSubscription.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/products/datasource/ProductsFilterAssemblerSubscription.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.products.datasource +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.products.datasource import androidx.compose.runtime.Composable import androidx.compose.runtime.remember diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainSection.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainSection.kt index da4dd5f1d8..ac265072da 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainSection.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainSection.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt index 7408397c0a..3a2eb48e96 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainZapSendDialog.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -117,6 +117,7 @@ import com.vitorpamplona.amethyst.commons.resources.onchain_send_title import com.vitorpamplona.amethyst.commons.resources.onchain_send_to import com.vitorpamplona.amethyst.commons.resources.onchain_send_use_note_split import com.vitorpamplona.amethyst.commons.resources.send +import com.vitorpamplona.amethyst.commons.ui.components.namecoin.NamecoinResolutionRow import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.commons.ui.note.UserPicture @@ -129,7 +130,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.bitcoinColor import com.vitorpamplona.amethyst.commons.util.formatGrouped import com.vitorpamplona.amethyst.commons.util.showAmount import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.ui.components.namecoin.NamecoinResolutionRow import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle @@ -141,13 +141,14 @@ import com.vitorpamplona.quartz.nipBCOnchainZaps.builder.OnchainZapBuilder import com.vitorpamplona.quartz.nipBCOnchainZaps.chain.FeeEstimates import com.vitorpamplona.quartz.utils.BigDecimal import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.compose.resources.StringResource /** Shared with the profile Send Payment screen's on-chain rail. */ -internal enum class FeeTier( +enum class FeeTier( val labelRes: StringResource, val etaLabelRes: StringResource, ) { @@ -156,7 +157,7 @@ internal enum class FeeTier( FAST(Res.string.onchain_send_fee_fast, Res.string.onchain_send_fee_eta_fast), } -internal fun FeeEstimates.rateFor(tier: FeeTier): Double = +fun FeeEstimates.rateFor(tier: FeeTier): Double = when (tier) { FeeTier.SLOW -> slowSatPerVbyte FeeTier.NORMAL -> normalSatPerVbyte diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletScreen.kt index 603addad9d..f92c537ad7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/WalletScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletScreen.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.wallet +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background @@ -70,7 +70,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.clink.ui.ClinkBudgetDialog import com.vitorpamplona.amethyst.commons.hashtags.Cashu import com.vitorpamplona.amethyst.commons.hashtags.CustomHashTagIcons @@ -102,6 +101,7 @@ import com.vitorpamplona.amethyst.commons.resources.wallet_sats import com.vitorpamplona.amethyst.commons.resources.wallet_save import com.vitorpamplona.amethyst.commons.resources.wallet_set_default import com.vitorpamplona.amethyst.commons.ui.components.rememberLongNotice +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.platform.AppBottomBar import com.vitorpamplona.amethyst.commons.ui.pluralStringRes @@ -121,10 +121,10 @@ fun WalletScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val walletViewModel: WalletViewModel = viewModel() + val walletViewModel: WalletViewModel = rememberViewModel { WalletViewModel() } walletViewModel.init(accountViewModel) - val cashuWalletViewModel: CashuWalletViewModel = viewModel() + val cashuWalletViewModel: CashuWalletViewModel = rememberViewModel { CashuWalletViewModel() } // Synchronous init so state-flow getters don't hit a null `account` on // the first composition pass. init() is idempotent — just sets refs. cashuWalletViewModel.init(accountViewModel) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.kt new file mode 100644 index 0000000000..3b37a76947 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.kt @@ -0,0 +1,40 @@ +/* + * 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.ui.wallet + +import androidx.compose.runtime.Composable + +/** Hands a payment to a wallet app installed on the device. */ +fun interface WalletAppLauncher { + /** + * Opens [invoice] (BOLT-11) in the wallet app. [onPaid] runs once the wallet took it, which is + * not a payment confirmation; [onError] gets [noWalletFound] when no wallet can open it. + */ + fun payInvoice( + invoice: String, + noWalletFound: String, + onPaid: () -> Unit, + onError: (String) -> Unit, + ) +} + +@Composable +expect fun rememberWalletAppLauncher(): WalletAppLauncher diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.ios.kt new file mode 100644 index 0000000000..9ecd3a05f9 --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.ios.kt @@ -0,0 +1,43 @@ +/* + * 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.ui.wallet + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalUriHandler +import kotlin.coroutines.cancellation.CancellationException + +/** Opens the `lightning:` link with whatever the system registered for it. */ +@Composable +actual fun rememberWalletAppLauncher(): WalletAppLauncher { + val uriHandler = LocalUriHandler.current + return remember(uriHandler) { + WalletAppLauncher { invoice, noWalletFound, onPaid, onError -> + try { + uriHandler.openUri("lightning:$invoice") + onPaid() + } catch (e: Exception) { + if (e is CancellationException) throw e + onError(noWalletFound) + } + } + } +} diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.jvm.kt new file mode 100644 index 0000000000..9ecd3a05f9 --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/wallet/WalletAppLauncher.jvm.kt @@ -0,0 +1,43 @@ +/* + * 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.ui.wallet + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalUriHandler +import kotlin.coroutines.cancellation.CancellationException + +/** Opens the `lightning:` link with whatever the system registered for it. */ +@Composable +actual fun rememberWalletAppLauncher(): WalletAppLauncher { + val uriHandler = LocalUriHandler.current + return remember(uriHandler) { + WalletAppLauncher { invoice, noWalletFound, onPaid, onError -> + try { + uriHandler.openUri("lightning:$invoice") + onPaid() + } catch (e: Exception) { + if (e is CancellationException) throw e + onError(noWalletFound) + } + } + } +} From ab517f3af53ca6451c13f631318ecf7b211106da Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 20:04:24 +0000 Subject: [PATCH 18/20] refactor: move podcast show/trailer, geocache log and visualizer settings to commonsUI - BookmarkGroupItemOptions shares through rememberTextSharer instead of an ACTION_SEND intent. - DocumentSelectSingle (expect/actual) picks one document by MIME type; the trailer composer uses it, and the geocache log uses GallerySelectSingle(imagesOnly = true). - MediaUri.lastPathSegmentOrNull() replaces Uri.lastPathSegment in the composer view models. - rfc2822Date (commons, tested against java.time's RFC_1123_DATE_TIME) replaces java.time in PodcastComposerMedia; the MediaMetadataRetriever probe moves to the app-side PodcastAudioProbe. - The canvas-only FakeWaveformAnimation moves to commonsUI; the media3 variant stays in the app. - Moved (9 files): the audio visualizer settings, geocache find log, podcast show editor and podcast trailer screens with their view models. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../wavefront/FakeWaveformAnimation.kt | 74 +------------ .../amethyst/ui/navigation/AppNavigation.kt | 8 +- .../display/BookmarkGroupItemOptions.kt | 23 +--- .../loggedIn/music/NewMusicTrackViewModel.kt | 3 +- .../authoring/NewPodcastEpisodeScreen.kt | 1 + .../authoring/NewPodcastEpisodeViewModel.kt | 6 +- .../podcasts/authoring/PodcastAudioProbe.kt | 51 +++++++++ .../service/uploads/MediaUri.android.kt | 2 + .../amethyst/commons/podcasts/Rfc2822Date.kt | 52 +++++++++ .../commons/service/uploads/MediaUri.kt | 3 + .../commons/service/uploads/MediaUri.ios.kt | 8 ++ .../commons/service/uploads/MediaUri.jvm.kt | 8 ++ .../commons/podcasts/Rfc2822DateTest.kt | 44 ++++++++ .../ui/actions/uploads/FileSelect.android.kt | 15 +++ .../ui/actions/uploads/SelectFromFiles.kt | 7 ++ .../wavefront/FakeWaveformAnimation.kt | 100 ++++++++++++++++++ .../geocaches/log/LogGeocacheFindScreen.kt | 23 ++-- .../geocaches/log/LogGeocacheFindViewModel.kt | 6 +- .../authoring/EditPodcastShowScreen.kt | 6 +- .../authoring/EditPodcastShowViewModel.kt | 2 +- .../authoring/NewPodcastTrailerScreen.kt | 23 ++-- .../authoring/NewPodcastTrailerViewModel.kt | 7 +- .../authoring/PodcastComposerMedia.kt | 41 ++----- .../podcasts/authoring/V4VSplitEditor.kt | 2 +- .../settings/AudioVisualizerSettingsScreen.kt | 4 +- .../ui/actions/uploads/FileSelect.ios.kt | 9 ++ .../ui/actions/uploads/FileSelect.jvm.kt | 9 ++ 27 files changed, 363 insertions(+), 174 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastAudioProbe.kt create mode 100644 commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822Date.kt create mode 100644 commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822DateTest.kt create mode 100644 commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/wavefront/FakeWaveformAnimation.kt rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt (98%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt (97%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt (92%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt (95%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt (75%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/podcasts/authoring/V4VSplitEditor.kt (99%) rename {amethyst/src/main/java/com/vitorpamplona/amethyst => commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons}/ui/screen/loggedIn/settings/AudioVisualizerSettingsScreen.kt (97%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/FakeWaveformAnimation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/FakeWaveformAnimation.kt index 1443c3ab45..f14983fde2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/FakeWaveformAnimation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/FakeWaveformAnimation.kt @@ -20,29 +20,15 @@ */ package com.vitorpamplona.amethyst.service.playback.composable.wavefront -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.requiredHeight -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableFloatState import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.CornerRadius -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Size -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.drawscope.Fill -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import androidx.media3.common.Player -import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn +import com.vitorpamplona.amethyst.commons.ui.components.wavefront.FakeWaveformAnimation import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -50,7 +36,6 @@ import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.onStart -import kotlin.math.sin @Composable fun FakeWaveformAnimation( @@ -97,60 +82,3 @@ fun pollCurrentPosition(controller: Player) = emit(controller.currentPosition) }.flowOn(Dispatchers.IO) .conflate() - -@Preview -@Composable -fun FakeWaveformAnimationPreview() { - val state = - remember { - mutableFloatStateOf((500 % 1000.0f) / 1000.0f) - } - ThemeComparisonColumn { - FakeWaveformAnimation(state, 50, Modifier.width(200.dp)) - } -} - -@Composable -fun FakeWaveformAnimation( - waveformProgress: MutableFloatState, - barCount: Int = 50, - modifier: Modifier = Modifier, -) { - val barBrush = - Brush.linearGradient( - colors = listOf(Color(0xff2598cf), Color(0xff652d80)), - start = Offset(0.2f * 128f, 0f), - end = Offset(0.2f * 128f + 128f, 0f), - ) - - Canvas( - modifier = - modifier - .fillMaxWidth() - .requiredHeight(48.dp) - .padding(horizontal = 10.dp), - ) { - val barWidth = 3.dp.toPx() - val barPadding = 2.dp.toPx() - val barRadius = 2.dp.toPx() - val totalBarWidth = barWidth + barPadding - val startX = (size.width - (barCount * totalBarWidth - barPadding)) / 2f - - for (i in 0 until barCount) { - val phase = (i.toFloat() / barCount) * 2f * Math.PI.toFloat() - val wave = sin(waveformProgress.floatValue * 2f * Math.PI.toFloat() + phase) - val barFraction = ((wave + 1f) / 2f) - val barHeight = size.height * barFraction - val x = startX + i * totalBarWidth - val y = (size.height - barHeight) / 2f - - drawRoundRect( - brush = barBrush, - topLeft = Offset(x, y), - size = Size(barWidth, barHeight), - cornerRadius = CornerRadius(barRadius, barRadius), - style = Fill, - ) - } - } -} 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 3ab4965e21..b7271fb601 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 @@ -121,6 +121,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.favorites.FavoriteA import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.followPacks.list.FollowPacksScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.GeocacheHuntScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.hunt.NewGeocacheHuntScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.log.LogGeocacheFindScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geohash.GeoHashScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.gitRepositories.GitRepositoriesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.hashtag.HashtagScreen @@ -143,6 +144,8 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.nsites.NsitesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.pinnednotes.PinnedNotesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.PodcastEpisodesScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.PodcastsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.EditPodcastShowScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.NewPodcastTrailerScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.PodcastAuthoringScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.PollsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.polls.results.PollResultsScreen @@ -158,6 +161,7 @@ import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.eventsync.Ev import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.nip43.RelayMembersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.vanish.VanishEventsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.AllSettingsScreen +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.AudioVisualizerSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BlockedUsersScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.BottomBarSettingsScreen import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.settings.CallSettingsScreen @@ -279,7 +283,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.followPacks.feed.FollowPack import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.GeocachesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.create.NewGeocacheScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.detail.GeocacheDetailScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.geocaches.log.LogGeocacheFindScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.GeoHashPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitNewIssueScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryCodeScreen @@ -306,9 +309,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationS import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages.NewPublicMessageScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.pictures.PicturesScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.PodcastScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.EditPodcastShowScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastEpisodeScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.podcasts.authoring.NewPodcastTrailerScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.polls.PollPostScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.ProfileScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.payment.SendPaymentScreen @@ -319,7 +320,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.subscriptions.Active import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.vanish.RequestToVanishScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.scheduledposts.ScheduledPostsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AudioVisualizerSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ComposeSettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NamecoinSettingsScreen diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt index b6b8eb9a21..5c658ad8ce 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/bookmarkgroups/display/BookmarkGroupItemOptions.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.bookmarkgroups.display -import android.content.Intent import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.getValue @@ -29,7 +28,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalClipboard -import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.Note @@ -60,6 +58,7 @@ import com.vitorpamplona.amethyst.commons.ui.components.GenericLoadable 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.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeEditDraftTo @@ -171,7 +170,7 @@ fun BookmarkGroupItemOptionsMenu( onDismiss = onDismiss, ) { val clipboardManager = LocalClipboard.current - val actContext = LocalContext.current + val sharer = rememberTextSharer() val scope = rememberCoroutineScope() // Bookmark Management section @@ -240,23 +239,7 @@ fun BookmarkGroupItemOptionsMenu( } } M3ActionRow(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, - externalLinkForNote(note), - ) - putExtra( - Intent.EXTRA_TITLE, - quickActionShareBrowserLinkStr, - ) - } - - val shareIntent = - Intent.createChooser(sendIntent, quickActionShareStr) - actContext.startActivity(shareIntent) + sharer.share(externalLinkForNote(note), quickActionShareBrowserLinkStr, quickActionShareStr) onDismiss() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt index 98dceec6da..b4332daaea 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/music/NewMusicTrackViewModel.kt @@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState +import com.vitorpamplona.amethyst.commons.service.uploads.lastPathSegmentOrNull import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -176,7 +177,7 @@ class NewMusicTrackViewModel : ViewModel() { audioMedia.value = MultiOrchestrator(persistentListOf(uri)) // Fall back to the last path segment of the content:// URI — Android picker URIs // rarely expose a clean filename, but the segment is at least stable across recomposes. - pickedAudioName.value = uri.uri.lastPathSegment?.substringAfterLast('/') + pickedAudioName.value = uri.uri.lastPathSegmentOrNull()?.substringAfterLast('/') // Auto-fill title/artist/album/duration from the picked file's embedded metadata so // the user doesn't have to retype what's already in the ID3 / mp4 / FLAC tags. diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt index 80256104c8..d8126aaeff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeScreen.kt @@ -98,6 +98,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.CoverImagePicker import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadInProgressBanner import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.music.UploadPlaceholder +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.V4VSplitEditor import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import kotlinx.collections.immutable.persistentListOf diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt index f0e20f62ae..5579d02896 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastEpisodeViewModel.kt @@ -33,6 +33,8 @@ import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.lastPathSegmentOrNull +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.PodcastComposerMedia import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nipXXPodcasting20.episode.Podcasting20EpisodeEvent @@ -154,11 +156,11 @@ class NewPodcastEpisodeViewModel : ViewModel() { return } audioMedia.value = MultiOrchestrator(persistentListOf(uri)) - pickedAudioName.value = uri.uri.lastPathSegment?.substringAfterLast('/') + pickedAudioName.value = uri.uri.lastPathSegmentOrNull()?.substringAfterLast('/') val appContext = context.applicationContext viewModelScope.launch(Dispatchers.IO) { - val probed = PodcastComposerMedia.probeAudio(appContext, uri.uri) ?: return@launch + val probed = PodcastAudioProbe.probeAudio(appContext, uri.uri) ?: return@launch withContext(Dispatchers.Main.immediate) { probed.durationSeconds?.let { durationSeconds.value = it.toString() } if (title.value.isBlank()) probed.title?.let { title.value = it } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastAudioProbe.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastAudioProbe.kt new file mode 100644 index 0000000000..2d1847a7c2 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastAudioProbe.kt @@ -0,0 +1,51 @@ +/* + * 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.screen.loggedIn.podcasts.authoring + +import android.content.Context +import android.media.MediaMetadataRetriever +import android.net.Uri +import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring.PodcastComposerMedia + +object PodcastAudioProbe { + /** + * Reads duration + title from a picked audio file via [MediaMetadataRetriever] (heavy — call off + * the main thread). Returns null if the provider rejects it or the file isn't a real container. + */ + fun probeAudio( + context: Context, + uri: Uri, + ): PodcastComposerMedia.ProbedAudio? { + val retriever = MediaMetadataRetriever() + return try { + retriever.setDataSource(context, uri) + val durationMs = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() + PodcastComposerMedia.ProbedAudio( + durationSeconds = durationMs?.let { (it / 1000).toInt().takeIf { secs -> secs > 0 } }, + title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)?.trim()?.ifBlank { null }, + ) + } catch (_: Exception) { + null + } finally { + retriever.release() + } + } +} diff --git a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.android.kt b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.android.kt index 5a7e949672..b38e335f3f 100644 --- a/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.android.kt +++ b/commons/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.android.kt @@ -23,3 +23,5 @@ package com.vitorpamplona.amethyst.commons.service.uploads import android.net.Uri actual typealias MediaUri = Uri + +actual fun MediaUri.lastPathSegmentOrNull(): String? = lastPathSegment diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822Date.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822Date.kt new file mode 100644 index 0000000000..86ea1ca0b0 --- /dev/null +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822Date.kt @@ -0,0 +1,52 @@ +/* + * 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.podcasts + +private val WEEKDAYS = arrayOf("Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed") +private val MONTHS = arrayOf("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") + +/** + * [epochSeconds] as an RFC 2822 date in GMT (`Tue, 24 Jun 2025 12:00:00 GMT`), the form + * Podcasting 2.0's `pubdate` and RSS use. Matches `java.time`'s `RFC_1123_DATE_TIME`, which + * does not zero-pad the day. + */ +fun rfc2822Date(epochSeconds: Long): String { + val days = epochSeconds.floorDiv(86_400L) + val secondOfDay = epochSeconds.mod(86_400L) + + // Civil-from-days (Howard Hinnant), proleptic Gregorian. + val z = days + 719_468L + val era = z.floorDiv(146_097L) + val doe = z - era * 146_097L + val yoe = (doe - doe / 1_460L + doe / 36_524L - doe / 146_096L) / 365L + val doy = doe - (365L * yoe + yoe / 4L - yoe / 100L) + val mp = (5L * doy + 2L) / 153L + val day = doy - (153L * mp + 2L) / 5L + 1L + val month = if (mp < 10L) mp + 3L else mp - 9L + val year = yoe + era * 400L + if (month <= 2L) 1L else 0L + + val weekday = WEEKDAYS[days.mod(7L).toInt()] + val hh = (secondOfDay / 3_600L).toString().padStart(2, '0') + val mm = (secondOfDay / 60L % 60L).toString().padStart(2, '0') + val ss = (secondOfDay % 60L).toString().padStart(2, '0') + + return "$weekday, $day ${MONTHS[(month - 1L).toInt()]} $year $hh:$mm:$ss GMT" +} diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt index f7459cf988..daef1f6d3a 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt @@ -29,3 +29,6 @@ expect abstract class MediaUri { /** The address as a string (`content://…` on Android). */ abstract override fun toString(): String } + +/** The last path segment of this address, if it has one (on Android, `Uri.lastPathSegment`). */ +expect fun MediaUri.lastPathSegmentOrNull(): String? diff --git a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt index eebaa3945a..e367e7b8f9 100644 --- a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt @@ -30,3 +30,11 @@ class StringMediaUri( ) : MediaUri() { override fun toString(): String = value } + +actual fun MediaUri.lastPathSegmentOrNull(): String? = + toString() + .substringBefore('?') + .substringBefore('#') + .trimEnd('/') + .substringAfterLast('/') + .ifBlank { null } diff --git a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt index eebaa3945a..e367e7b8f9 100644 --- a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt @@ -30,3 +30,11 @@ class StringMediaUri( ) : MediaUri() { override fun toString(): String = value } + +actual fun MediaUri.lastPathSegmentOrNull(): String? = + toString() + .substringBefore('?') + .substringBefore('#') + .trimEnd('/') + .substringAfterLast('/') + .ifBlank { null } diff --git a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822DateTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822DateTest.kt new file mode 100644 index 0000000000..8e1eff305a --- /dev/null +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/podcasts/Rfc2822DateTest.kt @@ -0,0 +1,44 @@ +/* + * 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.podcasts + +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import kotlin.test.Test +import kotlin.test.assertEquals + +class Rfc2822DateTest { + private fun javaTime(epochSeconds: Long) = DateTimeFormatter.RFC_1123_DATE_TIME.format(Instant.ofEpochSecond(epochSeconds).atZone(ZoneId.of("GMT"))) + + @Test + fun knownDate() { + assertEquals("Tue, 24 Jun 2025 12:00:00 GMT", rfc2822Date(1_750_766_400L)) + } + + @Test + fun matchesJavaTimeAcrossDates() { + val samples = + listOf(0L, 59L, 86_399L, 86_400L, 951_782_400L, 951_868_799L, 1_709_164_800L, 4_107_542_399L, 1_750_766_400L) + + (0 until 2_000).map { it * 2_629_743L + it * 7_919L } + samples.forEach { assertEquals(javaTime(it), rfc2822Date(it), "epoch $it") } + } +} diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt index 64f47649c6..dccb258f2a 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt @@ -24,6 +24,7 @@ import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -69,3 +70,17 @@ actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { LaunchFilePicker() } + +@Composable +actual fun DocumentSelectSingle( + mimeTypes: List, + onPicked: (SelectedMedia?) -> Unit, +) { + val resolver = LocalContext.current.contentResolver + val launcher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.OpenDocument(), + onResult = { uri: Uri? -> onPicked(uri?.let { SelectedMedia(it, resolver.getType(it)) }) }, + ) + LaunchedEffect(Unit) { launcher.launch(mimeTypes.toTypedArray()) } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromFiles.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromFiles.kt index 652b4963c9..c4458c2768 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromFiles.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/SelectFromFiles.kt @@ -91,3 +91,10 @@ private fun FileSelectButton( /** Opens the platform's document picker for audio and PDF files; reports an empty list on cancel. */ @Composable expect fun FileSelect(onFilesSelected: (ImmutableList) -> Unit = {}) + +/** Opens the platform's document picker for one file of [mimeTypes]; reports null on cancel. */ +@Composable +expect fun DocumentSelectSingle( + mimeTypes: List, + onPicked: (SelectedMedia?) -> Unit, +) diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/wavefront/FakeWaveformAnimation.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/wavefront/FakeWaveformAnimation.kt new file mode 100644 index 0000000000..b445c7c879 --- /dev/null +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/wavefront/FakeWaveformAnimation.kt @@ -0,0 +1,100 @@ +/* + * 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.ui.components.wavefront + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableFloatState +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.Fill +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn +import kotlin.math.PI +import kotlin.math.sin + +@Preview +@Composable +fun FakeWaveformAnimationPreview() { + val state = + remember { + mutableFloatStateOf((500 % 1000.0f) / 1000.0f) + } + ThemeComparisonColumn { + FakeWaveformAnimation(state, 50, Modifier.width(200.dp)) + } +} + +@Composable +fun FakeWaveformAnimation( + waveformProgress: MutableFloatState, + barCount: Int = 50, + modifier: Modifier = Modifier, +) { + val barBrush = + Brush.linearGradient( + colors = listOf(Color(0xff2598cf), Color(0xff652d80)), + start = Offset(0.2f * 128f, 0f), + end = Offset(0.2f * 128f + 128f, 0f), + ) + + Canvas( + modifier = + modifier + .fillMaxWidth() + .requiredHeight(48.dp) + .padding(horizontal = 10.dp), + ) { + val barWidth = 3.dp.toPx() + val barPadding = 2.dp.toPx() + val barRadius = 2.dp.toPx() + val totalBarWidth = barWidth + barPadding + val startX = (size.width - (barCount * totalBarWidth - barPadding)) / 2f + + for (i in 0 until barCount) { + val phase = (i.toFloat() / barCount) * 2f * PI.toFloat() + val wave = sin(waveformProgress.floatValue * 2f * PI.toFloat() + phase) + val barFraction = ((wave + 1f) / 2f) + val barHeight = size.height * barFraction + val x = startX + i * totalBarWidth + val y = (size.height - barHeight) / 2f + + drawRoundRect( + brush = barBrush, + topLeft = Offset(x, y), + size = Size(barWidth, barHeight), + cornerRadius = CornerRadius(barRadius, barRadius), + style = Fill, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt index ac8b6974b9..ad5b20eccf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt @@ -18,11 +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.geocaches.log +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.log -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring @@ -65,7 +62,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.font.FontWeight 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.resources.Res @@ -87,6 +83,8 @@ import com.vitorpamplona.amethyst.commons.resources.geocache_log_scan_code import com.vitorpamplona.amethyst.commons.resources.geocache_log_your_log import com.vitorpamplona.amethyst.commons.resources.geocache_unnamed import com.vitorpamplona.amethyst.commons.resources.route_log_geocache_find +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.rememberGeocachePalette @@ -121,7 +119,7 @@ fun LogGeocacheFindScreen( GeocachesFilterAssemblerSubscription(accountViewModel) val address = remember(kind, pubKeyHex, dTag) { Address(kind, pubKeyHex, dTag) } - val model: LogGeocacheFindViewModel = viewModel(key = "LogGeocacheFind-${address.toValue()}") + val model: LogGeocacheFindViewModel = rememberViewModel(key = "LogGeocacheFind-${address.toValue()}") { LogGeocacheFindViewModel() } val scope = rememberCoroutineScope() remember(address) { @@ -132,12 +130,15 @@ fun LogGeocacheFindScreen( val listing = model.listing() var scanning by remember { mutableStateOf(false) } - val photoPicker = - rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> - if (uri != null) { - scope.launch { model.uploadImage(uri, null, accountViewModel.host.mediaUploader) } + var pickingPhoto by remember { mutableStateOf(false) } + if (pickingPhoto) { + GallerySelectSingle(imagesOnly = true) { media -> + pickingPhoto = false + if (media != null) { + scope.launch { model.uploadImage(media.uri, media.mimeType, accountViewModel.host.mediaUploader) } } } + } Scaffold( topBar = { @@ -219,7 +220,7 @@ fun LogGeocacheFindScreen( OutlinedButton( enabled = !model.isUploading.value, onClick = { - photoPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + pickingPhoto = true }, ) { if (model.isUploading.value) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt index f231ebdc51..d41a08d49f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindViewModel.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.geocaches.log +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.geocaches.log -import android.net.Uri import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel @@ -28,6 +27,7 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.cache.LocalCache import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader +import com.vitorpamplona.amethyst.commons.service.uploads.MediaUri import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel @@ -183,7 +183,7 @@ class LogGeocacheFindViewModel : ViewModel() { } suspend fun uploadImage( - uri: Uri, + uri: MediaUri, mimeType: String?, uploader: MediaUploader, ): Boolean { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt index d831b4b5f2..f758022943 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowScreen.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.podcasts.authoring +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -47,7 +47,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.podcast_edit_show import com.vitorpamplona.amethyst.commons.resources.podcast_publishing_banner @@ -74,6 +73,7 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_show_type_serial import com.vitorpamplona.amethyst.commons.resources.podcast_show_website_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar @@ -95,7 +95,7 @@ fun EditPodcastShowScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val vm: EditPodcastShowViewModel = viewModel() + val vm: EditPodcastShowViewModel = rememberViewModel { EditPodcastShowViewModel() } LaunchedEffect(accountViewModel) { vm.init(accountViewModel) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt index 3a7ca74282..bf30181a6f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/EditPodcastShowViewModel.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.podcasts.authoring +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt index d356eab08f..cc3a236e0c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerScreen.kt @@ -18,11 +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.podcasts.authoring +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring -import android.net.Uri -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -50,12 +47,10 @@ import androidx.compose.runtime.remember 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.font.FontWeight import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType 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.resources.Res @@ -72,6 +67,8 @@ import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_upload_hint import com.vitorpamplona.amethyst.commons.resources.podcast_trailer_url_label import com.vitorpamplona.amethyst.commons.service.upload.ui.StrippingFailureDialog import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.ui.actions.uploads.DocumentSelectSingle +import com.vitorpamplona.amethyst.commons.ui.components.rememberViewModel import com.vitorpamplona.amethyst.commons.ui.insets.imePaddingSafe import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.SendingTopBar @@ -87,7 +84,7 @@ fun NewPodcastTrailerScreen( accountViewModel: AccountViewModel, nav: INav, ) { - val vm: NewPodcastTrailerViewModel = viewModel() + val vm: NewPodcastTrailerViewModel = rememberViewModel { NewPodcastTrailerViewModel() } LaunchedEffect(accountViewModel) { vm.init(accountViewModel) } StrippingFailureDialog(vm.strippingFailureConfirmation) @@ -201,14 +198,6 @@ fun NewPodcastTrailerScreen( } } -// Audio or video file via OpenDocument. +// Audio or video file via the document picker. @Composable -private fun MediaFileSelect(onPicked: (SelectedMedia?) -> Unit) { - val resolver = LocalContext.current.contentResolver - val launcher = - rememberLauncherForActivityResult( - contract = ActivityResultContracts.OpenDocument(), - onResult = { uri: Uri? -> onPicked(uri?.let { SelectedMedia(it, resolver.getType(it)) }) }, - ) - LaunchedEffect(Unit) { launcher.launch(arrayOf("audio/*", "video/*")) } -} +private fun MediaFileSelect(onPicked: (SelectedMedia?) -> Unit) = DocumentSelectSingle(listOf("audio/*", "video/*"), onPicked) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt index a57f1e1f57..d9b5a530a2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/NewPodcastTrailerViewModel.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.podcasts.authoring +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel @@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.commons.service.upload.SuspendableConfirmation import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.lastPathSegmentOrNull import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel import com.vitorpamplona.quartz.nipXXPodcasting20.trailer.Podcasting20TrailerEvent import kotlinx.collections.immutable.persistentListOf @@ -78,7 +79,7 @@ class NewPodcastTrailerViewModel : ViewModel() { return } media.value = MultiOrchestrator(persistentListOf(uri)) - pickedName.value = uri.uri.lastPathSegment?.substringAfterLast('/') + pickedName.value = uri.uri.lastPathSegmentOrNull()?.substringAfterLast('/') pickedMimeType.value = uri.mimeType } @@ -149,7 +150,7 @@ class NewPodcastTrailerViewModel : ViewModel() { _completionEvents.tryEmit(Unit) } catch (t: Throwable) { - accountViewModel.toastManager.toast("Failed to publish trailer", t.message ?: t.javaClass.simpleName) + accountViewModel.toastManager.toast("Failed to publish trailer", t.message ?: t::class.simpleName ?: "") } finally { withContext(Dispatchers.Main.immediate) { isSending.value = false } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt similarity index 75% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt index 43cdbf5921..014e23554a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/PodcastComposerMedia.kt @@ -18,13 +18,11 @@ * 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.podcasts.authoring +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring -import android.content.Context -import android.media.MediaMetadataRetriever -import android.net.Uri import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName +import com.vitorpamplona.amethyst.commons.podcasts.rfc2822Date import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.MultiOrchestrator @@ -32,10 +30,9 @@ import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes import com.vitorpamplona.amethyst.commons.ui.uploads.errorResource -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import java.util.UUID +import com.vitorpamplona.quartz.utils.TimeUtils +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid /** * Shared upload + media-probe mechanics for the Podcasting-2.0 composers (episode, show, trailer). @@ -97,34 +94,12 @@ object PodcastComposerMedia { val title: String?, ) - /** - * Reads duration + title from a picked audio file via [MediaMetadataRetriever] (heavy — call off - * the main thread). Returns null if the provider rejects it or the file isn't a real container. - */ - fun probeAudio( - context: Context, - uri: Uri, - ): ProbedAudio? { - val retriever = MediaMetadataRetriever() - return try { - retriever.setDataSource(context, uri) - val durationMs = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLongOrNull() - ProbedAudio( - durationSeconds = durationMs?.let { (it / 1000).toInt().takeIf { secs -> secs > 0 } }, - title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)?.trim()?.ifBlank { null }, - ) - } catch (_: Exception) { - null - } finally { - retriever.release() - } - } - /** Current time as an RFC2822 date string (`Tue, 24 Jun 2025 12:00:00 GMT`) — the spec's `pubdate`. */ - fun rfc2822Now(): String = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneId.of("GMT"))) + fun rfc2822Now(): String = rfc2822Date(TimeUtils.now()) /** A fresh, stable `d` tag for a new addressable episode/trailer. */ - fun generateDTag(prefix: String): String = "$prefix-${System.currentTimeMillis() / 1000}-${UUID.randomUUID().toString().take(8)}" + @OptIn(ExperimentalUuidApi::class) + fun generateDTag(prefix: String): String = "$prefix-${TimeUtils.now()}-${Uuid.random().toString().take(8)}" /** Splits a comma-separated text field into a clean list (trimmed, no blanks). */ fun parseCsv(text: String): List = text.split(',').map { it.trim() }.filter { it.isNotEmpty() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/V4VSplitEditor.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/V4VSplitEditor.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/V4VSplitEditor.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/V4VSplitEditor.kt index 13be6b657e..ecdbe4a50e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/podcasts/authoring/V4VSplitEditor.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/podcasts/authoring/V4VSplitEditor.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.podcasts.authoring +package com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.podcasts.authoring import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AudioVisualizerSettingsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AudioVisualizerSettingsScreen.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AudioVisualizerSettingsScreen.kt rename to commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AudioVisualizerSettingsScreen.kt index dc1a974b7e..fb72de7df4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/AudioVisualizerSettingsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/AudioVisualizerSettingsScreen.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.clickable @@ -64,11 +64,11 @@ import com.vitorpamplona.amethyst.commons.resources.audio_visualizer_settings import com.vitorpamplona.amethyst.commons.resources.audio_visualizer_settings_description import com.vitorpamplona.amethyst.commons.resources.audio_visualizer_static import com.vitorpamplona.amethyst.commons.resources.audio_visualizer_waves +import com.vitorpamplona.amethyst.commons.ui.components.wavefront.FakeWaveformAnimation import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.commons.ui.stringRes import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel -import com.vitorpamplona.amethyst.service.playback.composable.wavefront.FakeWaveformAnimation import kotlinx.coroutines.flow.Flow @Composable diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt index 24c979e0ef..766d009d92 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.ios.kt @@ -31,3 +31,12 @@ import kotlinx.collections.immutable.persistentListOf actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { LaunchedEffect(Unit) { onFilesSelected(persistentListOf()) } } + +/** No document picker wired yet: reports a cancel. */ +@Composable +actual fun DocumentSelectSingle( + mimeTypes: List, + onPicked: (SelectedMedia?) -> Unit, +) { + LaunchedEffect(Unit) { onPicked(null) } +} diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt index 24c979e0ef..766d009d92 100644 --- a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.jvm.kt @@ -31,3 +31,12 @@ import kotlinx.collections.immutable.persistentListOf actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { LaunchedEffect(Unit) { onFilesSelected(persistentListOf()) } } + +/** No document picker wired yet: reports a cancel. */ +@Composable +actual fun DocumentSelectSingle( + mimeTypes: List, + onPicked: (SelectedMedia?) -> Unit, +) { + LaunchedEffect(Unit) { onPicked(null) } +} From d313a69e0a2bab572dc06c9a4a63d85e93eddc28 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 20:15:51 +0000 Subject: [PATCH 19/20] chore: restore the strings-migrate __pycache__ files to main's copies Running the migrate script regenerated the tracked .pyc files; they carry no change. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../__pycache__/fix_escapes.cpython-311.pyc | Bin 10670 -> 6042 bytes .../__pycache__/migrate.cpython-311.pyc | Bin 11113 -> 11113 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tools/strings-migrate/__pycache__/fix_escapes.cpython-311.pyc b/tools/strings-migrate/__pycache__/fix_escapes.cpython-311.pyc index 8e90cd5b8b852b0c6eadfb710621e327ef368479..c700f979382f32152d995cd486765698f0b55997 100644 GIT binary patch delta 1656 zcmZ`(TWDNG7@j$o?Ow9GXKzW|Y?IBkd!RP0sn=R*OlmBYRGOk#tlLfIq&>OpS?8Q> zYDj4wB27!h$e^uNFb`VMmQu-!Prm4rzBE#>42V!sC`DL^g7~8Ue>N$p;J|z{=l}ni z|MLBFZj4;%EqoS@h6#-K@86jCBu>aL_^~mztZ&w?(02>hM_D5aT45HlA}nl$#{(>~ zpdW2u4Im3!(Q%DM7qp`>u!=pfii1@gtPGGE3-oBhO0vYHg7@-amRul~GLhPF$5P@Z zZ9AJ*h?TL@S1BRaVAcmqP5K*dfL57k+>l%3VLM7dpRm#_z4_)-^m^thc)jL(#`P(>_stMQ1`JnOV4qt)^ZFI9?fR2|Q9MNhaj{2g3z ziOf+xBvxbH`tCWhOvP$EBaFB~+eCZZ%!KC%Q1}-v+ahsp9{S(t5?18N1rmO3|i$e zhuP&593724Xr6U%n9AB+fcKP3#M`8;v$wUY`MmmYmz<$0k={RDTa6;tCa{ul6OF}Y>zWT||@Vs&Ec zMV_DMhDf!wclLv!&*oqA*SZ-)Yj9#TT6e0nC|)#9HT`$9r*$ z`AFS%na!m(Q<|ET0oUOkq=8{dy~6FXXGUNH4e4@CjCDO3Kp*@uvD}q5{I=GnOLm#> z1L;dE-*&ZAniRivA8*}-H_+1hyYtJy`|v2jm|#8Iv-NOLHUMB!zqBSjVm65{dr}86 zMBv()u0Y`=9pNUL;@^xnBg9azLgl44>Nr#D4dYcSP-c8lQibUKO!z+0yDj^C1cjP) z+V=P$Uf}!39R3bIJMFOQl=U1RflusqcM-6rQc7!DhH5o}uw7i-X78*8BGjxAfTccv qS@!(prt2^Ng`D`j(6PgysTu*mBe38RSn$Y(X49@mw3zQ`7~a3Rdx~-Z delta 5646 zcmaJlYit`wdb{M3l=#-mdfD=bvJ%meWl478I&$PwB-?p7^3|5ljwP8~a#s>9YL_>= zv@D$rvW4#=3&PpSHn1*8VD@mA^CiaBXi&s=$f1`a*SZfBqRK+T0tN~c`H>&j3h)&O z{G;vn&60ZMa0Bi)voqg(^Ue2OzIX5!=h}Ye^SL=_!$0`dl^-^6+<)Rrsg)N8PpWNi zx82X!IGMj9loln|hdjr943E_=xp%CW?O69nyQOm3E4ktE$lfuh!jLA8!}4=rMerOY`aol@IB8n-U z#MNX{Olh)4U4q$r4dviD<}%n1q8R zS%D)-GNAx-$hJJDQaE)&oknDZ^eGZm6A}?M#ttI31Gf_Fbu$h!nv9DgNy?&>P{tW8 z5~H#>Nm9u)Tsbj5ps*l-3=YlA_@bGBg3g*5Wjg0r|q!k_or~3j0}&&~%c?4`hp%5_O=e z=pu@FD|ad-x)^IOGNdZW8G-^#iZg0jCn>5*={OK=6Nw}z6ETpOE)zwbMj3#rL8J*? zmoo|(QGlUWk-APrMN5L@L2?P;E2*hPy=}XQsGoZ z)xm$*$nIwXNW9~7mWqfgEp7eK!LjI2KA+Ym-dpq zkZCoYl!!#dX=Jf2M5Yq*6)i53StT*4eDR~-{`pLZ#AGc2iU{fQl&H^aT?%)&2R6ka zOGiuRf}}#sGD2bCHKFNf*PFKkUR29+L@=;PIi(xJj(^L{)(w*peRTiu2pv&ifNwY) z-9OuO%o4~*Z1{5T=>CzIB2LLKh1!NMhen?ZhfD!Y-E>;QGo4h{(^QEw*DpFeMlm}) zPwF6ky~W+(B>pCMm7}(s+&NB1BxA+1r){1a<0adTvYXt8u>0c;3JcsE|7Wo|Ub0KV z_igo@(GYAh{@dfXYc)5Gd`0bU$+10#;0DPl*|#D)0h!AQ#^=te4EN0q$Vu2Wd$$iv zc!r6KX>Ut}m;i5M_JWqYp~WP)ga%e8$q)r}@Zt`)n?M`3#4%cmMUp%wrjz;s@F;N{ z+`X@Y&5Gbeu1Cp$eVU#D^xM^Zw$Q_LCZ27U~;r@ZXp}yhxMlt{aqKK>DdGZ4Q z@A7xJIWC8HHGby_x+UI<#9FJ7_0^&1Ms)|^728dIyZDM>+a}o*kU7Xj7^J8SlY7dO zu-$ZMx=3}?4)MaYCuJpUGx;fs3f2&fWQ~{0U(7r$JC+ouVv^W9`;4N}DKVLtl_Q%1 z?Kpu<%|&3WouFo#pOT{8jBowLh=FU;jY-sOetQ!>+%5{#Vcc^2ldLej9(Z z@AJeLiItu+%ROgu-MQ}N^B1$+6WGTMeuXljm`O8V{U5Zj!z@4D_vV@NLy-&TOuLqj znXZJ!g1PBTqFtxJVp$)yu`5uNB)dnHP(O^#>OaIit%Ic(5OoOvFud;O%BpTl%dQ6a ztp%zw^Pb0^>RT@@ytG_@ar1ZSa^4ducp}+>HJ5MMRlgRfxgERx-P^In+0{VvN}xF( zXek6*);Yd>-&$4eot}4kmb83TOQEV|e!xPG-;OVSZ#58F354>2eTBfjKZx8ju;vM5 zU#CG}Z^-ne70f&lmfEy9>Z;VrV8ER7G{_F@hNn|T(%H&i0$XXb(-U(>bFkL<$XQX* zsk=&>I8KLs#@pVeLCHSHNrL1+XDhYs{=40}z~i@)@s;*$l59hg+eysXw!d2>KY7OW z&wOUqHqu2afDEk!5Oz^7bS6Y`Fj8$gp--4)n5o$)n1%!!7gKU1B}1rJbko~6IB@pN zs{@fUgQwr5)kp?a8@5~4fOoP?0%Zh_ZP`TGSmIg){ggH%#z}azMF3z2m0VfPI%jj7 z;Ei1Q!OWL|y+8G?lFk*<`Jm>Z@bFI`(uYHzx$~sIK>G86fkI$lxoqIEr}5u+cmB=p z)vmskuD;*2KB~@l4Hmivm%sg;{O${d-52tniv`ccW#J-I=r>%SDX;?=V2%%csTu#` zEHj8t$VfXR2yShk{nuz~YZOD22zfT4=wrl^&PI4sutI^#KvR=fW$i^0ZSC48;hs{l z5bf&hg!f|9E7n2;{7lmmdKwBcC|4k$Ml-F0`?To6D0EVkr6;IUfMl@}vr4rn5EheA zZnZ-yh9U$5X$;d8E+CglC}*S@C?0gIo3@8tM4q*^wm#)s!@TLDatdOz9OkXSbOZx~ zr98S9h76pCfri=!wRt&j@Yb<~V@twPJnsn=JfUSFM1#if%YQFWwDqstq2sN9YuceK zF?ofeR2R+K&HTAErM5%h`LTS+75V^jaNLUoRI&S2AraF@+5f$RJZwEuNBmCY4HI(n#a*%xfmY>85? zwdeuC;(*IJF;nn(=7GICa)D&~CUs<@?L%PmV`P}M>pRd&^Enri4s+nY+nBc4NpRQg zI?AKia^;BK!s#`o84h+z_WQzi@6UNyYWpuwAhezA05>{vUC>db#g_9Ss)L&oa(;`I zowD7R+)QqtE-MV&~Z ztP0Q$Sea1nJ!lnLTF14iWVD^gNvP?+cfDiQRm3x$*5Iilt&e36{K?OG3~f-VL%C{2 zz(!yp958LFMi0QybV&({<(ladW03VQ7RE$P#wof75rivhLNT4JP}I!wb3OivMEu`Jn zhdGNW;JKO;-qB@BPUEvmrHFMm+7HVzJX#1WCd+;43oKr^6d3U$;Tew;*nKP15v1Qt-5_dOB7-9rtGPo}&fNQOJ*u%C$hn zeEDO4aB=)rav}La_xnfg9)Iuny%+M0-G#>P2e14(_SyJq@8C-B;3GZXJ5=Z$%KOh3 z{O7Z$82wwl3%yIvT7ki$mqCJSZf~~#v2WL^Z|{n4?@xu?g$L8i&Vb9P5N1M~djK*MT)tOQ7|;%Bu#uDw_BmkmE@$OqaBf%bU;ayMr6 z_O<5F=Hh(+ZQ+jl9rse#+rGc>b>zKGU$1%m+1IR;uu(39?69~)-+XcyWcU{Umh&x_ z@u}w(o9-|aXG8UZOR_IG=biJed43Fv>KkR(?Z#7Q$msIc@E)Th*kFj>qdW@>#xJ}L zU9=O3(Jlmd%(lRV#TKhP-+{B%brM(D_BWMO-+Q(%bj2pu}eHkYlko;xIbj9J5U>paQ0c#XXfs%P= zvsvmlTKvrt9;w|009E3be@y9*gLO+4cWd9P%~kxU;r)i(=PO=e{p|`NM;S!-LE48;RzFVj(CRYMfc@sT~BUajA z8ZsUD#Vm)*n7=ZnDJC}hIs)`ovyA02_@9EligT|4(6CM*%DuhQ}C|^6=7-Nlj~K@JRdafR!5u8 z!i33-)>jWdYIM) zDG}G1J(zYV^_b4smCUk81Oqd$Ac%DQDubF;MGKG^bc*4a1sQfl_^_LT|74PKFHOS& zcs-{bgs`w~=XrizAiS{7A$XbRc!TU3&-{mX*Zj+iuPxVh=e>st-b2~)HGkcFZ1Ku+ z{fl{jPr=`l^{hL6d~lruu%xl>UO(#|9%9{2K)n2${5ps5Heg_%)2;p?Y{d6R#rgH; z*{d~KgiTJqWeL`y%UK;8ZvmCYr@KPm_VIxwJDwfL!KI+v2}mVhwax)RE}%mupnIUP PZYS7pxN2_+Xq^8KI5Y6X diff --git a/tools/strings-migrate/__pycache__/migrate.cpython-311.pyc b/tools/strings-migrate/__pycache__/migrate.cpython-311.pyc index 7d06e126e42be8071e139eae546e01591c20df68..5e750571e75fb5d9f486196a1e5963d650639377 100644 GIT binary patch delta 20 acmaDE_A-onIWI340}y Date: Tue, 29 Sep 2026 21:00:10 +0000 Subject: [PATCH 20/20] fix: audit of the one-UI migration branch Bugs: - Single-image uploads (avatar, banner, emoji packs, relay group, long-form cover) report a metadata-strip failure as "Metadata could not be removed" again instead of a generic upload error, and long-form covers are not stripped, as before the move to uploadToDefaultServer. - AndroidMediaUploader moves to Dispatchers.IO itself: the calendar event composer called it from a Main-bound scope, hashing and decoding images on the UI thread. - Chat "retry without encryption" did nothing in hold mode: the retry state was only captured for send. - Pickers (gallery, file, document) keep their launched flag across an activity recreation, and the Concord and geocache pickers keep their open flag, so a picked image is no longer dropped or the picker reopened after a rotation. - Android's DateSkeletonFormatter keys its cache on the time zone, and the wallet rows format month, day and time as one locale pattern again. - Deep-link query lookup drops a #fragment, as java.net.URI did. - Pasting an empty clipboard no longer wipes a typed invoice. - BuzzDmListViewModel decides "new channel" with one atomic insert. - StringMediaUri compares by value (temp-file cleanup compares URIs) and lastPathSegmentOrNull matches Android (decoded, null without a path). - Cordn attachments no longer log a false "could not delete" warning. - HiddenAccountsFeedViewModel.Factory accepts a supertype request again. Performance: - rememberViewModel's lambda overload builds its factory once per call site instead of per recomposition. - The upload video thumbnail decodes a scaled frame and releases its MediaMetadataRetriever. - Active subscriptions polling runs in the screen's effect, so revisits no longer stack loops. - Zap button configs are remembered and marked @Immutable. Also drops dead code (an unused formatter and Context locals) and adds tests for the query lookup, MediaUri and the month/day/time skeleton. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P --- .../service/uploads/AndroidMediaUploader.kt | 11 ++-- .../ui/note/LocalizedDateTimeFormat.kt | 36 ++----------- .../chats/cordnGroup/CordnGroupChatScreen.kt | 9 +++- .../privateDM/send/ChatNewMessageViewModel.kt | 10 +++- .../chats/privateDM/send/NewGroupDMScreen.kt | 1 - .../nip23LongForm/LongFormPostScreen.kt | 2 - .../nip23LongForm/LongFormPostViewModel.kt | 4 +- .../loggedIn/home/ShortNotePostViewModel.kt | 1 - .../notifications/donations/ZapTheDevsCard.kt | 5 +- .../ActiveSubscriptionsScreen.kt | 2 +- .../ActiveSubscriptionsViewModel.kt | 19 ++++--- .../commons/service/uploads/MediaUri.kt | 44 +++++++++++++++ .../commons/service/uploads/MediaUri.ios.kt | 14 ++--- .../commons/service/uploads/MediaUri.jvm.kt | 14 ++--- .../commons/service/uploads/MediaUriTest.kt | 52 ++++++++++++++++++ .../ui/actions/uploads/FileSelect.android.kt | 25 ++++++--- .../actions/uploads/GallerySelect.android.kt | 20 ++++--- .../actions/uploads/VideoThumbnail.android.kt | 22 +++++--- .../commons/ui/note/MonthDayTime.android.kt | 42 +++++++++++++++ .../ui/note/PlatformDateFormat.android.kt | 24 ++++++--- .../ui/components/RememberViewModel.kt | 23 ++++++++ .../commons/ui/components/ZapButtonConfig.kt | 5 +- .../commons/ui/navigation/QueryParameters.kt | 5 +- .../amethyst/commons/ui/note/MonthDayTime.kt | 17 +++--- .../commons/ui/note/PlatformDateFormat.kt | 4 +- .../loggedIn/buzz/BuzzDmListViewModel.kt | 9 +++- .../concord/ConcordMetadataForm.kt | 5 +- .../dvms/DvmContentDiscoveryScreen.kt | 16 +++--- .../geocaches/log/LogGeocacheFindScreen.kt | 3 +- .../dal/HiddenAccountsFeedViewModel.kt | 9 ++-- .../wallet/OnchainTransactionsScreen.kt | 9 ++-- .../loggedIn/wallet/WalletSendScreen.kt | 2 +- .../wallet/WalletTransactionsScreen.kt | 9 ++-- .../ui/uploads/UploadToDefaultServer.kt | 24 +++++++-- .../ui/components/RememberViewModel.ios.kt | 14 +---- .../commons/ui/note/MonthDayTime.ios.kt | 30 +++++++++++ .../RememberViewModel.jvmAndroid.kt | 15 +----- .../commons/ui/note/MonthDayTime.jvm.kt | 30 +++++++++++ .../ui/navigation/QueryParametersTest.kt | 53 +++++++++++++++++++ .../ui/note/DateSkeletonFormatterTest.kt | 11 ++++ 40 files changed, 472 insertions(+), 178 deletions(-) create mode 100644 commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUriTest.kt create mode 100644 commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.android.kt create mode 100644 commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.ios.kt create mode 100644 commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.jvm.kt create mode 100644 commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParametersTest.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt index 293b53d998..2e670493a4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/AndroidMediaUploader.kt @@ -49,6 +49,7 @@ import com.vitorpamplona.quartz.utils.Log import com.vitorpamplona.quartz.utils.ciphers.NostrCipher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import java.io.File import kotlin.coroutines.cancellation.CancellationException @@ -56,7 +57,9 @@ import kotlin.coroutines.cancellation.CancellationException /** * Android's [MediaUploader]: compresses with LightCompressor/Compressor, strips metadata, encrypts, * and uploads over the app's upload-role OkHttp clients. Reads files through the application - * [appContext]'s content resolver, so it needs no Activity. + * [appContext]'s content resolver, so it needs no Activity. Every entry point moves to + * [Dispatchers.IO] itself: hashing, bitmap decoding and stripping block, and shared callers may + * launch from a Main-bound scope. */ class AndroidMediaUploader( private val appContext: Context, @@ -75,7 +78,7 @@ class AndroidMediaUploader( onStrippingFailed: suspend () -> Boolean, convertGifToMp4: Boolean, forcedSigner: NostrSigner?, - ): UploadingFinalState = Job(progress).upload(uri, mimeType, alt, contentWarningReason, compressionQuality, server, account, appContext, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) + ): UploadingFinalState = withContext(Dispatchers.IO) { Job(progress).upload(uri, mimeType, alt, contentWarningReason, compressionQuality, server, account, appContext, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) } override suspend fun uploadEncrypted( progress: UploadOrchestrator, @@ -92,7 +95,7 @@ class AndroidMediaUploader( onStrippingFailed: suspend () -> Boolean, convertGifToMp4: Boolean, forcedSigner: NostrSigner?, - ): UploadingFinalState = Job(progress).uploadEncrypted(uri, mimeType, alt, contentWarningReason, compressionQuality, encrypt, server, account, appContext, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) + ): UploadingFinalState = withContext(Dispatchers.IO) { Job(progress).uploadEncrypted(uri, mimeType, alt, contentWarningReason, compressionQuality, encrypt, server, account, appContext, useH265, stripMetadata, onStrippingFailed, convertGifToMp4, forcedSigner) } override suspend fun compressIfNeeded( progress: UploadOrchestrator, @@ -101,7 +104,7 @@ class AndroidMediaUploader( compressionQuality: CompressorQuality, useH265: Boolean, convertGifToMp4: Boolean, - ): MediaCompressorResult = Job(progress).compressIfNeeded(uri, mimeType, compressionQuality, appContext, useH265, convertGifToMp4) + ): MediaCompressorResult = withContext(Dispatchers.IO) { Job(progress).compressIfNeeded(uri, mimeType, compressionQuality, appContext, useH265, convertGifToMp4) } /** One upload's run: the pipeline, reporting into [progress]. */ private class Job( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/LocalizedDateTimeFormat.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/LocalizedDateTimeFormat.kt index 70ecc2dc71..e58e919b6b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/LocalizedDateTimeFormat.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/LocalizedDateTimeFormat.kt @@ -22,40 +22,12 @@ package com.vitorpamplona.amethyst.ui.note import android.content.Context import android.text.format.DateFormat -import java.text.SimpleDateFormat import java.util.Date -import java.util.Locale -/** - * Date/time formatters that respect the user's Android system settings: - * - Date order (dd/mm/yyyy, mm/dd/yyyy, yyyy-mm-dd…) is derived from the - * active [Locale] via [DateFormat.getBestDateTimePattern]. - * - 12-hour vs 24-hour clock follows the system setting via - * [DateFormat.is24HourFormat] (which is the user's manual override on - * top of the locale default). - * - * All formatters here use Unicode LDML skeletons. `j` and `jm` are intentionally - * avoided — `j` only picks 12/24 hour from the *locale*, not the user override. - * We pick the time skeleton explicitly based on [DateFormat.is24HourFormat]. - */ - -private fun timeSkeleton(context: Context): String = if (DateFormat.is24HourFormat(context)) "Hm" else "hma" - -private fun bestPattern( - context: Context, - skeletonBase: String, - includeTime: Boolean, -): SimpleDateFormat { - val locale = Locale.getDefault() - val skeleton = if (includeTime) skeletonBase + timeSkeleton(context) else skeletonBase - return SimpleDateFormat(DateFormat.getBestDateTimePattern(locale, skeleton), locale) -} - -/** Locale-aware month + day + short time (e.g. "May 28, 14:32" / "28 May, 2:32 PM"). */ -fun formatMonthDayTime( - epochSeconds: Long, - context: Context, -): String = bestPattern(context, "MMMd", includeTime = true).format(Date(epochSeconds * 1000L)) +// Date/time formatters that respect the user's Android system settings: the date order comes +// from the active locale and the clock follows DateFormat.is24HourFormat (the user's manual +// override on top of the locale default). The shared month/day/time formatter lives in +// commonsUI (rememberMonthDayTimeFormatter). /** Locale-aware medium date (e.g. "May 28, 2026" / "28 May 2026" / "28.05.2026"). */ fun formatMediumDate( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt index d6cd50aaa4..f5815f5152 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/cordnGroup/CordnGroupChatScreen.kt @@ -1096,7 +1096,7 @@ private suspend fun sendAttachment( // same reason the voice path deletes its recording. Both calls no-op on the // user's own file. deleteTempFile(finalUri, uri) - deleteTempFile(compressed.uri, uri) + if (compressed.uri != finalUri) deleteTempFile(compressed.uri, uri) } } finally { // Always, not just on success: a failure leaves the dialog up to retry from, @@ -1342,5 +1342,10 @@ private fun deleteTempFile( ) { if (tempUri == originalUri) return val path = tempUri.path ?: return - if (!File(path).delete()) Log.w("CordnGroupChat") { "Could not delete temp file $path" } + try { + val file = File(path) + if (file.exists() && !file.delete()) Log.w("CordnGroupChat") { "Could not delete temp file $path" } + } catch (e: Exception) { + Log.w("CordnGroupChat", "Failed to delete temp file $path", e) + } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt index 783f35dacd..5059cfe763 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt @@ -486,6 +486,9 @@ class ChatNewMessageViewModel : encryptedUploadErrorTitle = title encryptedUploadErrorMessage = message pendingRetryMode = RetryMode.HOLD + pendingRetryOnError = onError + pendingRetryUploader = uploader + pendingRetryOnceUploaded = onceUploaded }, uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, @@ -556,7 +559,9 @@ class ChatNewMessageViewModel : dismissEncryptedUploadError() - if (room == null || uploadState == null || uploader == null) return + // HOLD only uploads; SEND also needs the room to send into. + if (uploadState == null || uploader == null) return + if (mode == RetryMode.SEND && room == null) return uploadState.encryptFiles = false @@ -576,13 +581,14 @@ class ChatNewMessageViewModel : } RetryMode.SEND -> { + val sendRoom = room ?: return@launchSigner ChatFileUploader(account).justUploadNIP17Unencrypted( uploadState, onError ?: accountViewModel.toastManager::toast, uploader, onStrippingFailed = strippingFailureConfirmation::awaitConfirmation, ) { - ChatFileSender(room, account).sendNIP17(it) + ChatFileSender(sendRoom, account).sendNIP17(it) draftTag.newVersion() onceUploaded?.invoke() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index c6e13b30be..93d0c369e0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -236,7 +236,6 @@ fun GroupDMScreenContent( nav: INav, ) { val scrollState = rememberScrollState() - val context = LocalContext.current Column(Modifier.fillMaxSize()) { Row(Modifier.fillMaxWidth().padding(horizontal = Size10dp).weight(1f)) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt index 838601fdc4..14a038c7db 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostScreen.kt @@ -69,7 +69,6 @@ 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.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization @@ -490,7 +489,6 @@ private fun MarkdownPostScreenBody( verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp), ) { - val uploadContext = LocalContext.current ImageVideoDescription( it, accountViewModel.account.settings.defaultFileServer, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt index 600be530c4..527a64cf98 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip23LongForm/LongFormPostViewModel.kt @@ -486,7 +486,9 @@ class LongFormPostViewModel : galleryUri: SelectedMedia, uploader: MediaUploader, onError: (String, String) -> Unit, - ): String? = uploadToDefaultServer(galleryUri, account, uploader, onError) + ): String? = + // Cover images were never stripped: keep them as picked, only compressed. + uploadToDefaultServer(galleryUri, account, uploader, onError, stripMetadata = false) fun upload( alt: String?, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index b689be1da0..629fb3cc2d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -1937,7 +1937,6 @@ open class ShortNotePostViewModel : val recording = voiceRecording ?: return val fileToUpload = activeFile ?: recording.file val waveform = activeWaveform ?: recording.amplitudes - val appContext = Amethyst.instance.appContext val uploadErrorTitle = loadStringRes(Res.string.upload_error_title) val uploadVoiceNip95NotSupported = loadStringRes(Res.string.upload_error_voice_message_nip95_not_supported) val uploadVoiceFailed = loadStringRes(Res.string.upload_error_voice_message_failed) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt index 8177855b07..980a70bba4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/donations/ZapTheDevsCard.kt @@ -267,10 +267,7 @@ fun ZapDonationButton( accountViewModel: AccountViewModel, nav: INav, ) { - val config = - ZapButtonConfig( - grayTint = grayTint, - ) + val config = remember(grayTint) { ZapButtonConfig(grayTint = grayTint) } ReusableZapButton( baseNote = baseNote, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt index 6c79da8714..488797bcfc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsScreen.kt @@ -127,7 +127,7 @@ fun ActiveSubscriptionsScreen( nav: INav, viewModel: ActiveSubscriptionsViewModel = viewModel(), ) { - LaunchedEffect(Unit) { viewModel.startPolling(accountViewModel.account.client) } + LaunchedEffect(Unit) { viewModel.pollWhileShown(accountViewModel.account.client) } val state by viewModel.state.collectAsStateWithLifecycle() Scaffold( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt index b12cbe3667..5e79ff23c3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/subscriptions/ActiveSubscriptionsViewModel.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.subscriptions import androidx.compose.runtime.Immutable import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.commons.model.topNavFeeds.IFeedTopNavPerRelayFilter import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.ExplainedFilter import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.SubPurpose @@ -35,8 +34,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext /** @@ -143,13 +140,15 @@ class ActiveSubscriptionsViewModel : ViewModel() { private val _state = MutableStateFlow(ActiveSubscriptionsState()) val state: StateFlow = _state.asStateFlow() - /** Polls while the screen is on. [REFRESH_MS] is slow enough to be free, fast enough to feel live. */ - fun startPolling(client: INostrClient) { - viewModelScope.launch(Dispatchers.Default) { - while (isActive) { - _state.value = snapshot(client) - delay(REFRESH_MS) - } + /** + * Polls until the caller's scope is cancelled. Run it from the screen's effect, so leaving the + * screen stops it; a loop in `viewModelScope` would outlive the screen, and each return to it + * would stack another. [REFRESH_MS] is slow enough to be free, fast enough to feel live. + */ + suspend fun pollWhileShown(client: INostrClient) { + while (true) { + _state.value = snapshot(client) + delay(REFRESH_MS) } } diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt index daef1f6d3a..b4de3123e9 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.kt @@ -32,3 +32,47 @@ expect abstract class MediaUri { /** The last path segment of this address, if it has one (on Android, `Uri.lastPathSegment`). */ expect fun MediaUri.lastPathSegmentOrNull(): String? + +/** + * The last path segment of a string address, percent-decoded, or null when it has no path + * (`content://host`, `https://host/`). Accepts backslash separators, so a Windows path works too. The + * string-backed platforms use it to match Android's `Uri.lastPathSegment`. + */ +internal fun lastPathSegmentOf(address: String): String? { + var path = address.substringBefore('#').substringBefore('?') + val schemeEnd = path.indexOf("://") + if (schemeEnd >= 0) { + val pathStart = path.indexOf('/', schemeEnd + 3) + if (pathStart < 0) return null + path = path.substring(pathStart) + } + val segment = path.replace('\\', '/').trimEnd('/').substringAfterLast('/') + return if (segment.isEmpty()) null else percentDecodeUtf8(segment) +} + +/** Decodes `%XX` escapes as UTF-8 bytes; malformed escapes are kept as written. */ +private fun percentDecodeUtf8(input: String): String { + if ('%' !in input) return input + val out = StringBuilder(input.length) + val bytes = ArrayList() + var i = 0 + while (i < input.length) { + val c = input[i] + val hex = if (c == '%' && i + 2 < input.length && isHex(input[i + 1]) && isHex(input[i + 2])) input.substring(i + 1, i + 3).toInt(16) else null + if (hex != null) { + bytes.add(hex.toByte()) + i += 3 + } else { + if (bytes.isNotEmpty()) { + out.append(bytes.toByteArray().decodeToString()) + bytes.clear() + } + out.append(c) + i++ + } + } + if (bytes.isNotEmpty()) out.append(bytes.toByteArray().decodeToString()) + return out.toString() +} + +private fun isHex(c: Char) = c in '0'..'9' || c in 'a'..'f' || c in 'A'..'F' diff --git a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt index e367e7b8f9..a8e33c608e 100644 --- a/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt +++ b/commons/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.ios.kt @@ -29,12 +29,12 @@ class StringMediaUri( val value: String, ) : MediaUri() { override fun toString(): String = value + + // Value equality, like android.net.Uri: the upload pipeline tells a temp file from the + // user's own by comparing addresses, and deletes the temp one. + override fun equals(other: Any?): Boolean = other is StringMediaUri && other.value == value + + override fun hashCode(): Int = value.hashCode() } -actual fun MediaUri.lastPathSegmentOrNull(): String? = - toString() - .substringBefore('?') - .substringBefore('#') - .trimEnd('/') - .substringAfterLast('/') - .ifBlank { null } +actual fun MediaUri.lastPathSegmentOrNull(): String? = lastPathSegmentOf(toString()) diff --git a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt index e367e7b8f9..a8e33c608e 100644 --- a/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt +++ b/commons/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUri.jvm.kt @@ -29,12 +29,12 @@ class StringMediaUri( val value: String, ) : MediaUri() { override fun toString(): String = value + + // Value equality, like android.net.Uri: the upload pipeline tells a temp file from the + // user's own by comparing addresses, and deletes the temp one. + override fun equals(other: Any?): Boolean = other is StringMediaUri && other.value == value + + override fun hashCode(): Int = value.hashCode() } -actual fun MediaUri.lastPathSegmentOrNull(): String? = - toString() - .substringBefore('?') - .substringBefore('#') - .trimEnd('/') - .substringAfterLast('/') - .ifBlank { null } +actual fun MediaUri.lastPathSegmentOrNull(): String? = lastPathSegmentOf(toString()) diff --git a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUriTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUriTest.kt new file mode 100644 index 0000000000..752605bc66 --- /dev/null +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/service/uploads/MediaUriTest.kt @@ -0,0 +1,52 @@ +/* + * 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.uploads + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotEquals +import kotlin.test.assertNull + +class MediaUriTest { + @Test + fun lastSegmentOfPathsAndUrls() { + assertEquals("song.mp3", StringMediaUri("/home/me/music/song.mp3").lastPathSegmentOrNull()) + assertEquals("My Song.mp3", StringMediaUri("file:///home/me/My%20Song.mp3").lastPathSegmentOrNull()) + assertEquals("café.png", StringMediaUri("https://host/a/caf%C3%A9.png?x=1#y").lastPathSegmentOrNull()) + assertEquals("clip.mp4", StringMediaUri("C:\\Users\\me\\clip.mp4").lastPathSegmentOrNull()) + assertEquals("dir", StringMediaUri("https://host/dir/").lastPathSegmentOrNull()) + assertEquals("100%", StringMediaUri("/tmp/100%").lastPathSegmentOrNull()) + } + + @Test + fun noPathIsNull() { + assertNull(StringMediaUri("content://host").lastPathSegmentOrNull()) + assertNull(StringMediaUri("https://host/").lastPathSegmentOrNull()) + assertNull(StringMediaUri("").lastPathSegmentOrNull()) + } + + @Test + fun comparesByValue() { + assertEquals(StringMediaUri("/a/b.png"), StringMediaUri("/a/b.png")) + assertEquals(StringMediaUri("/a/b.png").hashCode(), StringMediaUri("/a/b.png").hashCode()) + assertNotEquals(StringMediaUri("/a/b.png"), StringMediaUri("/a/c.png")) + } +} diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt index dccb258f2a..6c87ff3900 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/FileSelect.android.kt @@ -28,16 +28,17 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import java.util.concurrent.atomic.AtomicBoolean @Composable actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { - val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } + // Saveable: a picker open across an activity recreation must not be launched a second time. + var hasLaunched by rememberSaveable { mutableStateOf(false) } val resolver = LocalContext.current.contentResolver val launcher = @@ -50,14 +51,15 @@ actual fun FileSelect(onFilesSelected: (ImmutableList) -> Unit) { SelectedMedia(it, resolver.getType(it)) }.toImmutableList(), ) - hasLaunched.set(false) + hasLaunched = false }, ) @Composable fun LaunchFilePicker() { SideEffect { - if (!hasLaunched.getAndSet(true)) { + if (!hasLaunched) { + hasLaunched = true launcher.launch( arrayOf( "audio/*", @@ -76,11 +78,20 @@ actual fun DocumentSelectSingle( mimeTypes: List, onPicked: (SelectedMedia?) -> Unit, ) { + var hasLaunched by rememberSaveable { mutableStateOf(false) } val resolver = LocalContext.current.contentResolver val launcher = rememberLauncherForActivityResult( contract = ActivityResultContracts.OpenDocument(), - onResult = { uri: Uri? -> onPicked(uri?.let { SelectedMedia(it, resolver.getType(it)) }) }, + onResult = { uri: Uri? -> + hasLaunched = false + onPicked(uri?.let { SelectedMedia(it, resolver.getType(it)) }) + }, ) - LaunchedEffect(Unit) { launcher.launch(mimeTypes.toTypedArray()) } + LaunchedEffect(Unit) { + if (!hasLaunched) { + hasLaunched = true + launcher.launch(mimeTypes.toTypedArray()) + } + } } diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt index a2561364ac..28ea8b58d4 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/GallerySelect.android.kt @@ -28,16 +28,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import java.util.concurrent.atomic.AtomicBoolean @Composable actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { - val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } + // Saveable: a picker open across an activity recreation must not be launched a second time. + var hasLaunched by rememberSaveable { mutableStateOf(false) } val resolver = LocalContext.current.contentResolver val launcher = @@ -50,14 +51,15 @@ actual fun GallerySelect(onImageUri: (ImmutableList) -> Unit) { SelectedMedia(it, resolver.getType(it)) }.toImmutableList(), ) - hasLaunched.set(false) + hasLaunched = false }, ) @Composable fun LaunchGallery() { SideEffect { - if (!hasLaunched.getAndSet(true)) { + if (!hasLaunched) { + hasLaunched = true launcher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageAndVideo)) } } @@ -71,7 +73,8 @@ actual fun GallerySelectSingle( imagesOnly: Boolean, onImageUri: (SelectedMedia?) -> Unit, ) { - val hasLaunched by remember { mutableStateOf(AtomicBoolean(false)) } + // Saveable: a picker open across an activity recreation must not be launched a second time. + var hasLaunched by rememberSaveable { mutableStateOf(false) } val resolver = LocalContext.current.contentResolver val launcher = @@ -84,14 +87,15 @@ actual fun GallerySelectSingle( onImageUri(null) } - hasLaunched.set(false) + hasLaunched = false }, ) @Composable fun LaunchGallery() { SideEffect { - if (!hasLaunched.getAndSet(true)) { + if (!hasLaunched) { + hasLaunched = true launcher.launch(PickVisualMediaRequest(if (imagesOnly) ActivityResultContracts.PickVisualMedia.ImageOnly else ActivityResultContracts.PickVisualMedia.ImageAndVideo)) } } diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt index 57b3a1d9ce..a746baa6a4 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/actions/uploads/VideoThumbnail.android.kt @@ -25,6 +25,7 @@ import android.graphics.Bitmap import android.media.MediaMetadataRetriever import android.net.Uri import android.os.Build +import androidx.annotation.RequiresApi import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState @@ -56,19 +57,26 @@ actual fun rememberVideoThumbnail(media: SelectedMedia): ImageBitmap? { return thumb } +// A preview tile: a frame this size is plenty, where a full 4K frame would be ~33 MB. +private const val THUMB_MAX_SIDE = 1024 + /** - * Creates a bitmap thumbnail from video uri of the scheme type content:// + * Creates a bitmap thumbnail, at most [THUMB_MAX_SIDE] on its longer side, from a video uri of the + * scheme type content:// */ -fun createVideoThumb( +@RequiresApi(Build.VERSION_CODES.O_MR1) +private fun createVideoThumb( context: Context, uri: Uri, ): Bitmap? { - try { - val mediaMetadataRetriever = MediaMetadataRetriever() - mediaMetadataRetriever.setDataSource(context, uri) - return mediaMetadataRetriever.frameAtTime + val retriever = MediaMetadataRetriever() + return try { + retriever.setDataSource(context, uri) + retriever.getScaledFrameAtTime(-1, MediaMetadataRetriever.OPTION_CLOSEST_SYNC, THUMB_MAX_SIDE, THUMB_MAX_SIDE) } catch (ex: Exception) { Log.w("VideoThumbnail", "Couldn't create thumbnail, but the video can be uploaded", ex) + null + } finally { + retriever.release() } - return null } diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.android.kt new file mode 100644 index 0000000000..3e5b081006 --- /dev/null +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.android.kt @@ -0,0 +1,42 @@ +/* + * 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.ui.note + +import android.text.format.DateFormat +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext + +// Explicit hour skeletons, not `j`: `j` follows the locale, the user's 12/24-hour override does not. +private val monthDayTime24 = DateSkeletonFormatter("MMMdHm") +private val monthDayTime12 = DateSkeletonFormatter("MMMdhma") + +/** Reads the 12/24-hour setting on every call: it can change under a running app. */ +@Composable +actual fun rememberMonthDayTimeFormatter(): (epochSeconds: Long) -> String { + val context = LocalContext.current + return remember(context) { + { epochSeconds -> + val formatter = if (DateFormat.is24HourFormat(context)) monthDayTime24 else monthDayTime12 + formatter.format(epochSeconds * 1000L) + } + } +} diff --git a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.android.kt b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.android.kt index 385e7335e5..f4c13f3478 100644 --- a/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.android.kt +++ b/commonsUI/src/androidMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.android.kt @@ -28,25 +28,35 @@ import androidx.compose.ui.platform.LocalContext import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import java.util.TimeZone /** - * Per-thread cached [SimpleDateFormat] keyed off the current default [Locale]. + * Per-thread cached [SimpleDateFormat] keyed off the current default [Locale] and time zone. * * `SimpleDateFormat` is mutable and not thread-safe, and these formatters are read from both * the UI thread (composition) and background coroutines. `ThreadLocal` gives each thread its - * own instance: no locks, no allocation per call, and a lazy rebuild on locale change. + * own instance: no locks, and a lazy rebuild on a locale or time-zone change. A formatter fixes + * its zone when built, so without the zone in the key a trip across zones would keep printing + * dates in the old one. */ actual class DateSkeletonFormatter actual constructor( private val skeleton: String, ) { - private val cache = ThreadLocal>() + private class Cached( + val locale: Locale, + val zoneId: String, + val formatter: SimpleDateFormat, + ) + + private val cache = ThreadLocal() private fun get(): SimpleDateFormat { - val current = Locale.getDefault() + val locale = Locale.getDefault() + val zoneId = TimeZone.getDefault().id val cached = cache.get() - if (cached != null && cached.first == current) return cached.second - val fresh = SimpleDateFormat(DateFormat.getBestDateTimePattern(current, skeleton), current) - cache.set(current to fresh) + if (cached != null && cached.locale == locale && cached.zoneId == zoneId) return cached.formatter + val fresh = SimpleDateFormat(DateFormat.getBestDateTimePattern(locale, skeleton), locale) + cache.set(Cached(locale, zoneId, fresh)) return fresh } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.kt index 5902d14fb1..5542f13703 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.kt @@ -21,8 +21,12 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras import kotlin.reflect.KClass /** @@ -56,3 +60,22 @@ inline fun rememberViewModel( key: String? = null, noinline factory: () -> VM, ): VM = rememberViewModel(VM::class, key, factory) + +/** + * One [ViewModelProvider.Factory] per call site for the lambda overloads of [rememberViewModel], + * instead of a new object on every recomposition. It calls the latest [factory], which only runs + * when the store has no view model yet. + */ +@Composable +internal fun rememberLambdaFactory(factory: () -> VM): ViewModelProvider.Factory { + val currentFactory by rememberUpdatedState(factory) + return remember { + object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T = currentFactory() as T + } + } +} diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt index a1d2260b25..340404608d 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/ZapButtonConfig.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.commons.ui.components +import androidx.compose.runtime.Immutable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.Dp @@ -28,8 +29,10 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier import com.vitorpamplona.amethyst.commons.ui.theme.Size35dp /** - * Configuration for zap button behavior and appearance + * Configuration for zap button behavior and appearance. Never mutated after construction, so it + * is marked immutable for Compose (its `Modifier` and `List` fields would otherwise make it unstable). */ +@Immutable data class ZapButtonConfig( val grayTint: Color = Color.Gray, val iconSize: Dp = Size35dp, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParameters.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParameters.kt index 88f3856290..1faf16cf74 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParameters.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParameters.kt @@ -32,7 +32,7 @@ package com.vitorpamplona.amethyst.commons.ui.navigation * the group under whichever account happened to be current instead of switching first. * * Splitting on the first `?` gets the same answer for both shapes, and returns null for a - * bare `nevent1…` with no query at all. + * bare `nevent1…` with no query at all. A `#fragment` is dropped; a value may itself contain `=`. * * `UriParser` reads an opaque query correctly too, and is the * right tool when a URI is already known to be well-formed. It is not this one: it builds a @@ -41,7 +41,8 @@ package com.vitorpamplona.amethyst.commons.ui.navigation * deep-link path treats an unreadable uri as "no route" rather than as a crash. */ fun String.findQueryParameterValue(parameterName: String): String? { - val query = substringAfter('?', "") + // A raw `#` starts the fragment, which is not part of the query (as `java.net.URI` read it). + val query = substringBefore('#').substringAfter('?', "") if (query.isEmpty()) return null return query diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt index 313eb57144..5eb37d3c10 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.kt @@ -20,16 +20,13 @@ */ package com.vitorpamplona.amethyst.commons.ui.note -private val monthDay = DateSkeletonFormatter("MMMd") +import androidx.compose.runtime.Composable /** - * Locale-ordered month and day followed by [timeOfDay] ("May 28 14:32" / "28 May 2:32 PM"). Pass - * [rememberTimeOfDayFormatter] so the clock follows the system 12/24-hour setting. + * Formats an instant (epoch seconds) as the locale's month, day and short time in one pattern + * ("May 28, 14:32" / "28 May, 2:32 PM"), in the current time zone. Android follows the system + * 12/24-hour setting; the JVM and iOS follow the locale. Identity-stable, so it can key a + * `remember`. */ -fun formatMonthDayTime( - epochSeconds: Long, - timeOfDay: (epochMillis: Long) -> String, -): String { - val millis = epochSeconds * 1000L - return "${monthDay.format(millis)} ${timeOfDay(millis)}" -} +@Composable +expect fun rememberMonthDayTimeFormatter(): (epochSeconds: Long) -> String diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.kt index 94711c83d9..4b88cbbbff 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/PlatformDateFormat.kt @@ -25,8 +25,8 @@ import androidx.compose.runtime.Composable /** * Formats instants with a Unicode LDML date skeleton ("yMMMd", "MMMd", "yMMM"), letting the * platform pick the default locale's field order ("MMM d, y" in en-US, "d MMM y" in en-GB). - * Safe to call from any thread. Rebuilds itself when the default locale changes (the JVM actual - * also on a time-zone change; Android and iOS keep the zone they were built with). + * Safe to call from any thread. Rebuilds itself when the default locale or time zone changes (iOS + * keeps the zone it was built with). */ expect class DateSkeletonFormatter( skeleton: String, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt index de520c976d..f76b6b9ab6 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/buzz/BuzzDmListViewModel.kt @@ -329,8 +329,13 @@ class BuzzDmListViewModel : ViewModel() { .filterValues { it in scoped } var changed = false memberships.forEach { (channelId, relay) -> - if (memberChannels[channelId] == null) changed = true - memberChannels[channelId] = relay + // One atomic insert decides "new", so a concurrent seed of the same + // channel can't make this pass skip its row rebuild. + if (memberChannels.putIfAbsent(channelId, relay) == null) { + changed = true + } else { + memberChannels[channelId] = relay + } } // A kind-44101 takes the membership away: drop the row rather than leaving a // conversation the relay no longer lets us read. diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt index ebb6b3fbf2..5a69e72520 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/chats/publicChannels/concord/ConcordMetadataForm.kt @@ -47,6 +47,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -159,7 +160,7 @@ private fun ConcordIconHero( var uploading by remember { mutableStateOf(false) } val iconModel = rememberConcordImageModel(icon.value, accountViewModel) - var picking by remember { mutableStateOf(false) } + var picking by rememberSaveable { mutableStateOf(false) } if (picking) { GallerySelectSingle(imagesOnly = true) { media -> picking = false @@ -232,7 +233,7 @@ private fun ConcordBannerHero( var uploading by remember { mutableStateOf(false) } val bannerModel = rememberConcordImageModel(banner.value, accountViewModel) - var picking by remember { mutableStateOf(false) } + var picking by rememberSaveable { mutableStateOf(false) } if (picking) { GallerySelectSingle(imagesOnly = true) { media -> picking = false diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt index ceb76c6f5d..403708c1cc 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/dvms/DvmContentDiscoveryScreen.kt @@ -487,13 +487,15 @@ fun ZapDVMButton( nav: INav, ) { val config = - ZapButtonConfig( - grayTint = grayTint, - iconSize = iconSize, - showUserFinderSubscription = true, - zapAmountChoices = listOf(amount / 1000), - buttonText = "Zap ${(amount / 1000)} sats to the DVM", - ) + remember(grayTint, iconSize, amount) { + ZapButtonConfig( + grayTint = grayTint, + iconSize = iconSize, + showUserFinderSubscription = true, + zapAmountChoices = listOf(amount / 1000), + buttonText = "Zap ${(amount / 1000)} sats to the DVM", + ) + } ReusableZapButton( baseNote = baseNote, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt index ad5b20eccf..a74c53ffda 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/geocaches/log/LogGeocacheFindScreen.kt @@ -54,6 +54,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -130,7 +131,7 @@ fun LogGeocacheFindScreen( val listing = model.listing() var scanning by remember { mutableStateOf(false) } - var pickingPhoto by remember { mutableStateOf(false) } + var pickingPhoto by rememberSaveable { mutableStateOf(false) } if (pickingPhoto) { GallerySelectSingle(imagesOnly = true) { media -> pickingPhoto = false diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt index 26c897aabb..8b92bc28de 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/settings/dal/HiddenAccountsFeedViewModel.kt @@ -37,11 +37,10 @@ class HiddenAccountsFeedViewModel( modelClass: KClass, extras: CreationExtras, ): T { - if (modelClass == HiddenAccountsFeedViewModel::class) { - @Suppress("UNCHECKED_CAST") - return HiddenAccountsFeedViewModel(account) as T - } - throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.simpleName}") + // Like the sibling feed factories: a caller may ask for a supertype + // (`UserFeedViewModel`), which a class-equality check would reject. + @Suppress("UNCHECKED_CAST") + return HiddenAccountsFeedViewModel(account) as T } } } diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt index 80bf3a1f50..5887f121c3 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/OnchainTransactionsScreen.kt @@ -80,8 +80,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay -import com.vitorpamplona.amethyst.commons.ui.note.formatMonthDayTime -import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter +import com.vitorpamplona.amethyst.commons.ui.note.rememberMonthDayTimeFormatter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.datasource.OnchainZapsFilterAssemblerSubscription import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -312,11 +311,11 @@ private fun OnchainTransactionItem( (if (isIncoming) "+" else "-") + formatGrouped(amountSats) } - val timeOfDay = rememberTimeOfDayFormatter() + val monthDayTime = rememberMonthDayTimeFormatter() val dateText = - remember(view.tx.blockTime, view.tx.confirmations, timeOfDay) { + remember(view.tx.blockTime, view.tx.confirmations, monthDayTime) { val ts = view.tx.blockTime - if (ts != null) formatMonthDayTime(ts, timeOfDay) else "" + if (ts != null) monthDayTime(ts) else "" } val counterpartyPubkeyHex = view.counterpartyPubkeyHex() diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletSendScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletSendScreen.kt index 9687610c0d..0e8cf144d0 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletSendScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletSendScreen.kt @@ -130,7 +130,7 @@ fun WalletSendScreen( maxLines = 5, trailingIcon = { IconButton(onClick = { - clipboardScope.launch { invoiceText = clipboard.getText() ?: "" } + clipboardScope.launch { clipboard.getText()?.let { invoiceText = it } } }) { Icon( symbol = MaterialSymbols.ContentPaste, diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt index f2e3d61133..90cc9e89ea 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/screen/loggedIn/wallet/WalletTransactionsScreen.kt @@ -77,8 +77,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.note.LoadUser import com.vitorpamplona.amethyst.commons.ui.note.UserPicture import com.vitorpamplona.amethyst.commons.ui.note.UsernameDisplay -import com.vitorpamplona.amethyst.commons.ui.note.formatMonthDayTime -import com.vitorpamplona.amethyst.commons.ui.note.rememberTimeOfDayFormatter +import com.vitorpamplona.amethyst.commons.ui.note.rememberMonthDayTimeFormatter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.TransactionFilter import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.wallet.WalletViewModel import com.vitorpamplona.amethyst.commons.ui.stringRes @@ -281,10 +280,10 @@ private fun TransactionItem( (if (isIncoming) "+" else "-") + formatGrouped(amountSats) } - val timeOfDay = rememberTimeOfDayFormatter() + val monthDayTime = rememberMonthDayTimeFormatter() val dateText = - remember(tx.created_at, timeOfDay) { - tx.created_at?.let { formatMonthDayTime(it, timeOfDay) } ?: "" + remember(tx.created_at, monthDayTime) { + tx.created_at?.let { monthDayTime(it) } ?: "" } val directionLabel = diff --git a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt index 3b6fa75390..4e14ab5b59 100644 --- a/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt +++ b/commonsUI/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/uploads/UploadToDefaultServer.kt @@ -24,10 +24,13 @@ import com.vitorpamplona.amethyst.commons.model.Account import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details import com.vitorpamplona.amethyst.commons.resources.login_with_a_private_key_to_be_able_to_upload +import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_title +import com.vitorpamplona.amethyst.commons.resources.metadata_strip_failed_upload_cancelled import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url_after_uploading import com.vitorpamplona.amethyst.commons.service.uploads.CompressorQuality import com.vitorpamplona.amethyst.commons.service.uploads.MediaUploader import com.vitorpamplona.amethyst.commons.service.uploads.SelectedMedia +import com.vitorpamplona.amethyst.commons.service.uploads.UploadError import com.vitorpamplona.amethyst.commons.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.commons.service.uploads.UploadingState import com.vitorpamplona.amethyst.commons.ui.loadStringRes @@ -35,9 +38,10 @@ import com.vitorpamplona.quartz.nip01Core.signers.SignerExceptions import kotlinx.coroutines.CancellationException /** - * Compresses, strips (when the account asks for it) and uploads one picked file to the account's - * default media server, returning the hosted URL. Every failure, a metadata-stripping one - * included, is reported through [onError] with a user-facing title and message, and yields null. + * Compresses, strips (when [stripMetadata] and the account ask for it) and uploads one picked file + * to the account's default media server, returning the hosted URL. Every failure, a + * metadata-stripping one included, is reported through [onError] with a user-facing title and + * message, and yields null. * * For single-image fields (avatars, banners, pack and cover images) that only need a URL back. */ @@ -47,6 +51,7 @@ suspend fun uploadToDefaultServer( uploader: MediaUploader, onError: (title: String, message: String) -> Unit, quality: CompressorQuality = CompressorQuality.MEDIUM, + stripMetadata: Boolean = true, ): String? { val state = try { @@ -59,7 +64,7 @@ suspend fun uploadToDefaultServer( server = account.settings.defaultFileServer, account = account, uploader = uploader, - stripMetadata = account.settings.stripLocationOnUpload, + stripMetadata = stripMetadata && account.settings.stripLocationOnUpload, onStrippingFailed = { false }, ) } catch (_: SignerExceptions.ReadOnlyException) { @@ -83,7 +88,16 @@ suspend fun uploadToDefaultServer( } is UploadingState.Error -> { - onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(state.errorResource, *state.params)) + when (state.error) { + // The only cancel on this path is a metadata strip that failed: say so, since the + // user turned stripping on and would otherwise not know why nothing uploaded. + UploadError.UPLOAD_CANCELLED -> + onError(loadStringRes(Res.string.metadata_strip_failed_title), loadStringRes(Res.string.metadata_strip_failed_upload_cancelled)) + UploadError.AVIF_METADATA_STRIP_FAILED -> + onError(loadStringRes(Res.string.metadata_strip_failed_title), loadStringRes(state.errorResource, *state.params)) + else -> + onError(loadStringRes(Res.string.failed_to_upload_media_no_details), loadStringRes(state.errorResource, *state.params)) + } null } } diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.ios.kt index 089cdea849..9b52253b2f 100644 --- a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.ios.kt +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.ios.kt @@ -26,7 +26,6 @@ import androidx.compose.runtime.remember import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelStore -import androidx.lifecycle.viewmodel.CreationExtras import kotlin.reflect.KClass /** @@ -50,15 +49,4 @@ actual fun rememberViewModel( modelClass: KClass, key: String?, factory: () -> VM, -): VM = - rememberViewModel( - modelClass, - key, - object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create( - modelClass: KClass, - extras: CreationExtras, - ): T = factory() as T - }, - ) +): VM = rememberViewModel(modelClass, key, rememberLambdaFactory(factory)) diff --git a/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.ios.kt b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.ios.kt new file mode 100644 index 0000000000..f03895ab70 --- /dev/null +++ b/commonsUI/src/iosMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.ios.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.amethyst.commons.ui.note + +import androidx.compose.runtime.Composable + +private val monthDayTime = DateSkeletonFormatter("MMMdjm") + +private val formatMonthDayTime: (Long) -> String = { epochSeconds -> monthDayTime.format(epochSeconds * 1000L) } + +@Composable +actual fun rememberMonthDayTimeFormatter(): (epochSeconds: Long) -> String = formatMonthDayTime diff --git a/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.jvmAndroid.kt b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.jvmAndroid.kt index 7faaa0e1ee..e09bb2ea16 100644 --- a/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.jvmAndroid.kt +++ b/commonsUI/src/jvmAndroid/kotlin/com/vitorpamplona/amethyst/commons/ui/components/RememberViewModel.jvmAndroid.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.runtime.Composable import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.compose.viewModel import kotlin.reflect.KClass @@ -39,16 +38,4 @@ actual fun rememberViewModel( modelClass: KClass, key: String?, factory: () -> VM, -): VM = - viewModel( - modelClass = modelClass, - key = key, - factory = - object : ViewModelProvider.Factory { - @Suppress("UNCHECKED_CAST") - override fun create( - modelClass: KClass, - extras: CreationExtras, - ): T = factory() as T - }, - ) +): VM = viewModel(modelClass = modelClass, key = key, factory = rememberLambdaFactory(factory)) diff --git a/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.jvm.kt b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.jvm.kt new file mode 100644 index 0000000000..f03895ab70 --- /dev/null +++ b/commonsUI/src/jvmMain/kotlin/com/vitorpamplona/amethyst/commons/ui/note/MonthDayTime.jvm.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.amethyst.commons.ui.note + +import androidx.compose.runtime.Composable + +private val monthDayTime = DateSkeletonFormatter("MMMdjm") + +private val formatMonthDayTime: (Long) -> String = { epochSeconds -> monthDayTime.format(epochSeconds * 1000L) } + +@Composable +actual fun rememberMonthDayTimeFormatter(): (epochSeconds: Long) -> String = formatMonthDayTime diff --git a/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParametersTest.kt b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParametersTest.kt new file mode 100644 index 0000000000..b3b57ae7ca --- /dev/null +++ b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/navigation/QueryParametersTest.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.ui.navigation + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class QueryParametersTest { + @Test + fun readsOpaqueAndHierarchicalQueries() { + assertEquals("npub1abc", "marmot:0011?account=npub1abc".findQueryParameterValue("account")) + assertEquals("30078:ab:x", "connectedapp?coordinate=30078:ab:x&y=1".findQueryParameterValue("coordinate")) + assertEquals("1", "https://h/p?a=0&y=1".findQueryParameterValue("y")) + } + + @Test + fun dropsTheFragment() { + assertEquals("abc", "connectedapp?coordinate=abc#x".findQueryParameterValue("coordinate")) + assertNull("connectedapp#x?coordinate=abc".findQueryParameterValue("coordinate")) + } + + @Test + fun keepsEqualsInsideValues() { + assertEquals("https://h/p?q=1", "url?id=https://h/p?q=1".findQueryParameterValue("id")) + assertEquals("a=b", "x?k=a=b".findQueryParameterValue("k")) + } + + @Test + fun missingOrEmptyIsNull() { + assertNull("nevent1xyz".findQueryParameterValue("id")) + assertNull("x?id=".findQueryParameterValue("id")) + assertNull("x?other=1".findQueryParameterValue("id")) + } +} diff --git a/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/DateSkeletonFormatterTest.kt b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/DateSkeletonFormatterTest.kt index e95be9295e..3d3f110330 100644 --- a/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/DateSkeletonFormatterTest.kt +++ b/commonsUI/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/ui/note/DateSkeletonFormatterTest.kt @@ -75,6 +75,17 @@ class DateSkeletonFormatterTest { assertEquals("Jan 2024", DateSkeletonFormatter("yMMM").format(millis(2024, 1, 5))) } + @Test + fun monthDayTimeSkeletonIsOnePattern() { + // rememberMonthDayTimeFormatter's JVM skeleton: `j` must be accepted, and the locale's + // own separator kept between the date and the time. + Locale.setDefault(Locale.US) + val us = DateSkeletonFormatter("MMMdjm").format(millis(2024, 5, 28, hour = 14)) + assertTrue(us, us.startsWith("May 28,") && us.contains("2:00") && us.contains("PM")) + Locale.setDefault(Locale.UK) + assertTrue(DateSkeletonFormatter("MMMdjm").format(millis(2024, 5, 28, hour = 14)).contains("14:00")) + } + @Test fun calendarDayDistinguishesDaysAndYears() { assertEquals(calendarYearAndDay(millis(2024, 3, 1, hour = 1)), calendarYearAndDay(millis(2024, 3, 1, hour = 23)))