From ce7e6d31bcb37af9da23daa23a95a4bd2f35b91a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 29 Sep 2026 04:16:55 +0000 Subject: [PATCH] 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()) }