mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<String>): Location? =
|
||||
providers
|
||||
.mapNotNull { provider ->
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -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<String> = queryActivities(pm, viewIntent(CONTROL_URL)).mapNotNull { it.packageName() }.toSet()
|
||||
|
||||
/**
|
||||
|
||||
+1
-2
@@ -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.
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+3
-4
@@ -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> =
|
||||
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,
|
||||
|
||||
@@ -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 <T : Route> popUpTo(
|
||||
route: Route,
|
||||
klass: KClass<T>,
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
@@ -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?,
|
||||
|
||||
+1
-2
@@ -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(
|
||||
|
||||
+9
-9
@@ -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\"" }
|
||||
|
||||
+3
-3
@@ -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<HexKey>())
|
||||
private val observerSeen = ConcurrentSet<HexKey>()
|
||||
|
||||
/** 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<ObserverFrameEvent>()
|
||||
|
||||
+11
-6
@@ -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<String, NormalizedRelayUrl>()
|
||||
private val memberChannels = ConcurrentMap<String, NormalizedRelayUrl>()
|
||||
|
||||
private val _rows = MutableStateFlow<List<DmRow>>(emptyList())
|
||||
val rows: StateFlow<List<DmRow>> = _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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
|
||||
+3
-10
@@ -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))
|
||||
|
||||
+3
-3
@@ -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)
|
||||
|
||||
+1
-2
@@ -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) {
|
||||
|
||||
+7
-2
@@ -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 <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(ChessViewModel::class.java)) {
|
||||
override fun <T : ViewModel> create(
|
||||
modelClass: KClass<T>,
|
||||
extras: CreationExtras,
|
||||
): T {
|
||||
if (modelClass == ChessViewModel::class) {
|
||||
return ChessViewModel(
|
||||
userPubkey = account.userProfile().pubkeyHex,
|
||||
publisher = AndroidChessPublisher(account),
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
+1
-2
@@ -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) {
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+1
-2
@@ -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() {
|
||||
|
||||
+3
-20
@@ -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)) {
|
||||
|
||||
+3
-20
@@ -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)) {
|
||||
|
||||
+21
-12
@@ -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<ConnectedAppDetailState?>(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 = {
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+4
-3
@@ -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),
|
||||
|
||||
+3
-2
@@ -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 = {},
|
||||
|
||||
+4
-4
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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 }
|
||||
|
||||
+8
-3
@@ -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 <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(HiddenAccountsFeedViewModel::class.java)) {
|
||||
override fun <T : ViewModel> create(
|
||||
modelClass: KClass<T>,
|
||||
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}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+1
-2
@@ -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,
|
||||
|
||||
+7
-9
@@ -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 =
|
||||
|
||||
+3
-7
@@ -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
|
||||
}
|
||||
|
||||
+27
@@ -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
|
||||
+32
@@ -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()))
|
||||
+1
-6
@@ -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))
|
||||
+25
@@ -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
|
||||
@@ -6245,4 +6245,11 @@
|
||||
<string name="cordn_voice_stop">Stop and send</string>
|
||||
<string name="browser_address_hint">Search or enter address</string>
|
||||
<string name="napplet_untitled">Untitled nApplet</string>
|
||||
<string name="browser_permission_section">Site permissions</string>
|
||||
<string name="browser_permission_camera">Use your camera</string>
|
||||
<string name="browser_permission_microphone">Use your microphone</string>
|
||||
<string name="browser_permission_location">Know your approximate location</string>
|
||||
<string name="browser_permission_ask">Ask</string>
|
||||
<string name="browser_permission_allowed">Allowed</string>
|
||||
<string name="browser_permission_blocked">Blocked</string>
|
||||
</resources>
|
||||
|
||||
+3
@@ -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?,
|
||||
|
||||
+27
@@ -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
|
||||
+2
-1
@@ -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
|
||||
}
|
||||
|
||||
+23
@@ -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
|
||||
+23
@@ -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
|
||||
+1
-1
@@ -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 = {},
|
||||
|
||||
+2
@@ -38,6 +38,8 @@ expect class ConcurrentSet<E : Any>() {
|
||||
|
||||
fun size(): Int
|
||||
|
||||
fun clear()
|
||||
|
||||
/** A point-in-time copy — safe to iterate or diff against without a lock. */
|
||||
fun snapshot(): Set<E>
|
||||
}
|
||||
|
||||
+2
@@ -31,5 +31,7 @@ actual class ConcurrentSet<E : Any> {
|
||||
|
||||
actual fun size(): Int = set.size
|
||||
|
||||
actual fun clear() = set.clear()
|
||||
|
||||
actual fun snapshot(): Set<E> = HashSet(set)
|
||||
}
|
||||
|
||||
+2
@@ -42,5 +42,7 @@ actual class ConcurrentSet<E : Any> {
|
||||
|
||||
actual fun size(): Int = ref.load().size
|
||||
|
||||
actual fun clear() = ref.store(HashSet())
|
||||
|
||||
actual fun snapshot(): Set<E> = HashSet(ref.load())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user