mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
Merge pull request #4248 from vitorpamplona/claude/intelligent-babbage-23komv
refactor: one UI, waves 4–5 — Account, AccountViewModel, NoteCompose and RichTextViewer into commons/commonsUI
This commit is contained in:
+28
-10
@@ -11,9 +11,11 @@ contain implementations of Nostr specifications and utilities to help implement
|
||||
`concord/` (`cordXX`), `buzz/`, plus the binding-agnostic RFC 9420 engine in `mls/`. A new protocol
|
||||
over Nostr belongs here as a package, not as a Gradle module; `quic`/`nestsClient`/`marmotQuic` are
|
||||
modules because they are transports with no Nostr in them. Commons stores
|
||||
shared code between Amethyst Android (`amethyst`) and Amethyst Desktop (`desktopApp`). The Desktop
|
||||
App is designed to be mouse first and so uses a completely different screen and navigation
|
||||
architecture while sharing the back end components with the android counterpart. `cli` ships `amy`,
|
||||
shared code between Amethyst Android (`amethyst`) and Amethyst Desktop (`desktopApp`). Android now
|
||||
also ships on laptops, so the direction is **one UI**: every screen and the navigation shell at every
|
||||
window size move to `commonsUI`, `amethyst` shrinks to an Android shim, and a new `desktopApp`
|
||||
becomes a JVM shim that renders the same UI (see `commons/plans/2026-09-27-one-ui-android-desktop.md`).
|
||||
Until it lands, today's `desktopApp` still has its own mouse-first screens and navigation. `cli` ships `amy`,
|
||||
a non-interactive JVM command-line client that drives the same `quartz` + `commons` code — used by
|
||||
humans, agents, and interop tests. `quic` is a from-scratch pure-Kotlin QUIC v1 + HTTP/3 +
|
||||
WebTransport client (no JNI, no BouncyCastle), built because no Android-compatible Java QUIC library
|
||||
@@ -105,7 +107,9 @@ amethyst/
|
||||
codecs. Not WebTransport — the binding has its own ALPNs and writes frames
|
||||
straight onto QUIC streams, so it deliberately does not reuse
|
||||
`nestsClient`'s `WebTransportSession`.
|
||||
- `amethyst/` & `desktopApp/` = Platform-native layouts and navigation
|
||||
- `amethyst/` & `desktopApp/` = Platform shims: process/window entry points, services, system
|
||||
integrations and the actuals of shared ports. Screens and navigation are shared UI and are
|
||||
moving to `commonsUI` (today's `desktopApp` screens are legacy, to be replaced).
|
||||
- `cli/` = Thin assembly layer over `quartz/` + `commons/` (no new logic
|
||||
allowed). May also depend on `:geode` (for `amy serve`, which embeds the
|
||||
standalone relay); never on `:commonsUI`, `:amethyst` or `:desktopApp`.
|
||||
@@ -207,10 +211,21 @@ etc. instead of re-implementing them.
|
||||
(StateFlow/SharedFlow), so they belong in `commons`; anything that imports
|
||||
`androidx.compose.ui`/`foundation`/`material3`, Coil, or `Res` belongs in
|
||||
`commonsUI`.
|
||||
- **Keep native** → screen composables/scaffolding (Desktop `Window` vs Android
|
||||
`Activity`), navigation (sidebar vs bottom nav), platform interactions
|
||||
(gestures, keyboard shortcuts), system integrations (notifications, file
|
||||
pickers).
|
||||
**Screens and navigation are shared too**: screen composables, the nav host,
|
||||
and the navigation chrome for every window size (bottom bar, rail, permanent
|
||||
drawer) belong in `commonsUI`, because Android runs on laptops and the new
|
||||
Desktop app renders the same UI. Adapt to the window with `ScreenLayoutSpec`
|
||||
/ `LocalScreenLayout`, not with a per-platform screen.
|
||||
- **Keep native** → only what is the platform itself: the process/window entry
|
||||
point (Android `Activity`/`Service`, Desktop `Window`/tray/menu bar),
|
||||
system integrations (notifications, file pickers, share sheets, camera,
|
||||
media3, WebView, keyring/Keystore), and the platform `actual`s or port
|
||||
implementations the shared UI calls. A new screen goes in `commonsUI` when
|
||||
its dependencies allow — `AccountViewModel` itself is in `commonsUI`
|
||||
(`commons.viewmodels`), reaching Android through `AccountViewModelHost`. A
|
||||
screen that still needs an app-only helper may live in `amethyst/`, but keep
|
||||
Android APIs out of it (behind a port or slot) so it can move later. Don't add screens to today's `desktopApp`
|
||||
that the shared UI will have to re-create.
|
||||
|
||||
When extracting a composable: move it to `commonsUI/commonMain/` (see
|
||||
`/compose-expert`), add expect/actual for any platform behavior (see
|
||||
@@ -429,8 +444,11 @@ Do this before considering the task complete.
|
||||
at byte level (`perl -CSD -pe 's/\x{202E}/\\u202E/g'`).
|
||||
|
||||
### Navigation Shell
|
||||
- **Desktop**: Sidebar + main content area
|
||||
- **Android**: Bottom navigation
|
||||
One shell, picked by window size rather than by platform: `ScreenLayoutSpec`
|
||||
chooses the bottom bar (compact), the rail, or the permanent drawer (wide,
|
||||
landscape, tall enough), and docks the notification panel on very wide windows.
|
||||
It is moving to `commonsUI` with `AppNavigation`. Today's `desktopApp` still has
|
||||
its own sidebar shell, which will be replaced.
|
||||
|
||||
## Git Workflow
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: compose-expert
|
||||
description: Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates navigation to android-expert/desktop-expert. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).
|
||||
description: Advanced Compose Multiplatform UI patterns for shared composables. Use when working with visual UI components, state management patterns (remember, derivedStateOf, produceState), recomposition optimization (@Stable/@Immutable visual usage), Material3 theming, custom ImageVector icons, or determining whether to share UI in commonMain vs keep platform-specific. Delegates platform entry points (Activity, Window, tray) to android-expert/desktop-expert; the navigation shell itself is shared UI. Complements kotlin-expert (handles Kotlin language aspects of state/annotations).
|
||||
---
|
||||
|
||||
# Compose Multiplatform Expert
|
||||
@@ -34,12 +34,17 @@ Visual UI patterns for sharing composables across Android and Desktop.
|
||||
- **Theme utilities**: Color calculations, style helpers
|
||||
- **Material3 components**: Any UI using Material primitives
|
||||
|
||||
**Screens and navigation are shared too.** Android also ships on laptops and the new
|
||||
Desktop app renders the same UI, so screens, the nav host and the bottom bar / rail /
|
||||
permanent drawer all belong in `commonsUI`. They adapt to the window with
|
||||
`ScreenLayoutSpec` / `LocalScreenLayout`, not per platform. See
|
||||
`commons/plans/2026-09-27-one-ui-android-desktop.md`.
|
||||
|
||||
### Keep Platform-Specific
|
||||
|
||||
- **Navigation structure**: Bottom nav (Android) vs Sidebar (Desktop)
|
||||
- **Screen layouts**: Platform-specific scaffolding
|
||||
- **System integrations**: File pickers, notifications, share sheets
|
||||
- **Platform UX**: Gestures, keyboard shortcuts, window management
|
||||
- **Entry points**: Android `Activity`/`Service`, Desktop `Window`, tray, menu bar
|
||||
- **System integrations**: File pickers, notifications, share sheets, camera, media3, WebView
|
||||
- **Platform leaves inside shared UI**: behind expect/actual or a slot the shim fills
|
||||
|
||||
### Decision Framework
|
||||
|
||||
@@ -563,12 +568,14 @@ fun FeedList(items: List<Item>) {
|
||||
4. Add caching if generated dynamically
|
||||
5. Wrap in @Composable for easy use
|
||||
|
||||
### Navigation (Delegate)
|
||||
### Navigation
|
||||
|
||||
For navigation patterns:
|
||||
- Android bottom nav → `android-expert`
|
||||
- Desktop sidebar → `desktop-expert`
|
||||
- Multi-window → `desktop-expert`
|
||||
The navigation shell is shared (`ScreenLayoutSpec` picks bottom bar, rail or permanent
|
||||
drawer by window size) and is moving to `commonsUI` with `AppNavigation`. Delegate only
|
||||
the platform leaves:
|
||||
- Activity / intent plumbing → `android-expert`
|
||||
- Window, tray, menu bar, multi-window → `desktop-expert`
|
||||
- Today's legacy `desktopApp` sidebar shell → `desktop-expert` (to be replaced)
|
||||
|
||||
## Related Skills
|
||||
|
||||
|
||||
@@ -248,9 +248,16 @@ application {
|
||||
|
||||
## 5. Desktop Navigation Patterns
|
||||
|
||||
### NavigationRail (Current Pattern)
|
||||
### NavigationRail (Current Pattern — legacy)
|
||||
|
||||
Desktop uses **NavigationRail** (vertical sidebar) instead of Android's bottom navigation.
|
||||
> **Being replaced.** Android now also ships on laptops, and the plan is one UI: the
|
||||
> shared navigation shell (bottom bar / rail / permanent drawer picked by window size
|
||||
> via `ScreenLayoutSpec`) moves to `commonsUI`, and a new `desktopApp` becomes a JVM
|
||||
> shim that renders it. Don't grow this sidebar shell or add Desktop-only screens; see
|
||||
> `commons/plans/2026-09-27-one-ui-android-desktop.md`. What stays Desktop-specific is
|
||||
> the window, tray, menu bar, keyboard shortcuts and file system integration.
|
||||
|
||||
Today's desktop app uses **NavigationRail** (vertical sidebar) instead of Android's bottom navigation.
|
||||
|
||||
```kotlin
|
||||
Row(Modifier.fillMaxSize()) {
|
||||
|
||||
@@ -52,8 +52,9 @@ Q: Does it vary by platform or by JVM vs non-JVM?
|
||||
│ Example: Jackson JSON parsing (JVM library)
|
||||
│
|
||||
└─ Complex/UI-related
|
||||
→ Keep platform-specific
|
||||
Example: Navigation (Activity vs Window too different)
|
||||
→ Share it in commonsUI; expect/actual or a slot only for the platform leaf
|
||||
Example: Navigation. One shell in commonsUI, picked by window size
|
||||
(ScreenLayoutSpec); only the Activity / Window entry point is native
|
||||
|
||||
Final check:
|
||||
Q: Maintenance cost of abstraction < duplication cost?
|
||||
@@ -85,10 +86,13 @@ val jvmAndroid = create("jvmAndroid") {
|
||||
```
|
||||
**Why:** Jackson is JVM-only, works on Android + Desktop, not iOS/web.
|
||||
|
||||
**Navigation → platform-specific:**
|
||||
- Android: `MainActivity` (Activity + Compose Navigation)
|
||||
- Desktop: `Window` + sidebar + MenuBar
|
||||
**Why:** UI paradigms fundamentally different.
|
||||
**Navigation → shared, entry points → platform-specific:**
|
||||
- Shared (`commonsUI`, moving there): the nav host, bottom bar / rail /
|
||||
permanent drawer, chosen by window size via `ScreenLayoutSpec`
|
||||
- Android: `MainActivity` hosts it; Desktop: a `Window` (+ MenuBar, tray) hosts it
|
||||
**Why:** Android runs on laptops too, and the new Desktop app renders the same
|
||||
UI. See `commons/plans/2026-09-27-one-ui-android-desktop.md`. Today's
|
||||
`desktopApp` sidebar shell is legacy.
|
||||
|
||||
## Mental Model: Source Sets as Dependency Graph
|
||||
|
||||
@@ -183,15 +187,15 @@ Quick decision guidelines based on codebase patterns:
|
||||
### Sometimes Abstract
|
||||
- **Business logic:** YES - state machines, data processing
|
||||
- **ViewModels:** YES - state + business logic shareable (StateFlow/SharedFlow)
|
||||
- **Screen layouts:** NO - platform-native (Window vs Activity)
|
||||
- **Why:** ViewModels contain platform-agnostic state; Screens render differently per platform
|
||||
- **Screen layouts:** YES - shared in `commonsUI`; they adapt to window size, not platform
|
||||
- **Why:** One UI for Android (phones to laptops) and Desktop; only entry points differ
|
||||
|
||||
### Rarely Abstract
|
||||
- **Complex UI components** (composables with heavy platform dependencies)
|
||||
- **Why:** Platform paradigms can differ significantly
|
||||
|
||||
### Never Abstract
|
||||
- **Navigation** (Activity vs Window fundamentally different)
|
||||
- **Process / window entry points** (Activity, Service, Window, tray)
|
||||
- **Permissions** (Android vs iOS APIs incompatible)
|
||||
- **Platform UX patterns**
|
||||
- **Why:** Too platform-specific, abstraction creates leaky APIs
|
||||
@@ -203,7 +207,7 @@ Quick decision guidelines based on codebase patterns:
|
||||
| PubKeyFormatter, ZapFormatter | ✅ YES | Pure Kotlin, no platform APIs |
|
||||
| TimeAgoFormatter | ⚠️ ABSTRACTED | Needs StringProvider for localized strings |
|
||||
| ViewModels (state + logic) | ✅ YES | StateFlow/SharedFlow platform-agnostic, Compose Multiplatform lifecycle compatible |
|
||||
| Screen layouts (Scaffold, nav) | ❌ NO | Window vs Activity, sidebar vs bottom nav fundamentally different |
|
||||
| Screen layouts (Scaffold, nav) | ✅ YES (moving) | One UI; `ScreenLayoutSpec` picks bottom bar / rail / drawer by window size |
|
||||
| Image loading (Coil) | ⚠️ ABSTRACTED | Coil 3.x supports KMP, needs expect/actual wrapper |
|
||||
|
||||
## expect/actual Mechanics
|
||||
@@ -333,8 +337,9 @@ fun validateSignature(...) { ... } // Duplicated!
|
||||
// ❌ BAD
|
||||
expect fun NavigationComponent(...)
|
||||
```
|
||||
**Why:** Navigation paradigms too different (Activity vs Window)
|
||||
**Fix:** Keep platform-specific, accept duplication
|
||||
**Why:** It forks the whole navigation UI per platform.
|
||||
**Fix:** Write the navigation once in `commonsUI`; put only the platform leaf
|
||||
(an Activity result, a file picker) behind expect/actual or a slot
|
||||
|
||||
### 2. Under-Sharing
|
||||
**Problem:** Duplicating business logic across platforms
|
||||
@@ -384,8 +389,8 @@ import com.fasterxml.jackson.databind.ObjectMapper
|
||||
| **ViewModels** | **commons/commonMain/viewmodels/** | **StateFlow/SharedFlow + logic shareable, Compose MP lifecycle compatible** |
|
||||
| UI formatters (pure) | commons/commonMain | Reusable, no dependencies |
|
||||
| UI components (simple) | commonsUI/commonMain | Cards, buttons, dialogs (Compose UI never goes in `commons`) |
|
||||
| **Screen layouts** | **Platform-specific** | **Window vs Activity, sidebar vs bottom nav** |
|
||||
| Navigation | Platform-specific only | Activity vs Window too different |
|
||||
| **Screen layouts** | **commonsUI** | **One UI; adapt to window size with `ScreenLayoutSpec`** |
|
||||
| Navigation | commonsUI (entry point native) | Nav host + chrome shared; Activity / Window hosts it |
|
||||
| Permissions | Platform-specific only | APIs incompatible |
|
||||
| Platform UX (menus, etc.) | Platform-specific only | Native feel required |
|
||||
|
||||
|
||||
@@ -25,18 +25,19 @@ import android.graphics.Color
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.vitorpamplona.amethyst.commons.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.DEFAULT_MEDIA_SERVERS
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType
|
||||
import com.vitorpamplona.amethyst.commons.model.nipB7Blossom.BlossomServerListState
|
||||
import com.vitorpamplona.amethyst.commons.service.http.DefaultContentTypeInterceptor
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.nipB7Blossom.BlossomServerListState
|
||||
import com.vitorpamplona.amethyst.service.uploads.FileHeader
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.FileHeader
|
||||
import com.vitorpamplona.amethyst.service.uploads.ImageDownloader
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
||||
import com.vitorpamplona.amethyst.service.uploads.nip96.ServerInfoRetriever
|
||||
import com.vitorpamplona.amethyst.service.uploads.prepare
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
|
||||
+19
-6
@@ -21,19 +21,25 @@
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.InMemoryGeohashIdentityStore
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.location.LocationResult
|
||||
import com.vitorpamplona.amethyst.commons.model.marmot.MarmotGroupNotifier
|
||||
import com.vitorpamplona.amethyst.commons.model.nip46Signer.Nip46ConsentPrompter
|
||||
import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter
|
||||
import com.vitorpamplona.amethyst.commons.notifications.dal.NotificationFeedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.assemblers.CashuMintDirectoryFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.nip47WalletConnect.NWCPaymentFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.service.http.EncryptionKeyCache
|
||||
import com.vitorpamplona.amethyst.commons.service.http.OkHttpWebSocket
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.dal.NotificationFeedFilter
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.defaultMarmotStreamTransport
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder
|
||||
import com.vitorpamplona.quartz.nip60Cashu.mintApi.OkHttpMintTransport
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -83,14 +89,21 @@ class NotificationFeedFilterModeOverrideTest {
|
||||
Account(
|
||||
settings = AccountSettings(keyPair = keyPair),
|
||||
signer = NostrSignerInternal(keyPair),
|
||||
geolocationFlow = { MutableStateFlow<LocationState.LocationResult>(LocationState.LocationResult.Loading) },
|
||||
geolocationFlow = { MutableStateFlow<LocationResult>(LocationResult.Loading) },
|
||||
nwcFilterAssembler = { NWCPaymentFilterAssembler(client) },
|
||||
cashuMintDirectoryFilterAssembler = { CashuMintDirectoryFilterAssembler(client) },
|
||||
okHttpClientForMoney = { OkHttpClient() },
|
||||
cashuMintTransport = OkHttpMintTransport { OkHttpClient() },
|
||||
otsResolverBuilder = { EmptyOtsResolverBuilder.build() },
|
||||
cache = LocalCache,
|
||||
client = client,
|
||||
scope = scope,
|
||||
appVersion = "test",
|
||||
encryptionKeyCache = EncryptionKeyCache(),
|
||||
saveSettings = {},
|
||||
marmotNotifier = { MarmotGroupNotifier.None },
|
||||
nip46Consent = Nip46ConsentPrompter.Unanswered,
|
||||
geohashIdentityStore = InMemoryGeohashIdentityStore(),
|
||||
marmotStreamTransportFactory = ::defaultMarmotStreamTransport,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+18
-5
@@ -21,14 +21,19 @@
|
||||
package com.vitorpamplona.amethyst
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.InMemoryGeohashIdentityStore
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.location.LocationResult
|
||||
import com.vitorpamplona.amethyst.commons.model.marmot.MarmotGroupNotifier
|
||||
import com.vitorpamplona.amethyst.commons.model.nip46Signer.Nip46ConsentPrompter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.assemblers.CashuMintDirectoryFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.nip47WalletConnect.NWCPaymentFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.service.http.EncryptionKeyCache
|
||||
import com.vitorpamplona.amethyst.commons.service.http.OkHttpWebSocket
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.thread.ThreadFeedFilter
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.defaultMarmotStreamTransport
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.verify
|
||||
@@ -37,6 +42,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder
|
||||
import com.vitorpamplona.quartz.nip60Cashu.mintApi.OkHttpMintTransport
|
||||
import junit.framework.TestCase
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -73,14 +79,21 @@ class ThreadDualAxisChartAssemblerTest {
|
||||
Account(
|
||||
settings = AccountSettings(keyPair = keyPair),
|
||||
signer = NostrSignerInternal(keyPair),
|
||||
geolocationFlow = { MutableStateFlow<LocationState.LocationResult>(LocationState.LocationResult.Loading) },
|
||||
geolocationFlow = { MutableStateFlow<LocationResult>(LocationResult.Loading) },
|
||||
nwcFilterAssembler = { NWCPaymentFilterAssembler(client) },
|
||||
cashuMintDirectoryFilterAssembler = { CashuMintDirectoryFilterAssembler(client) },
|
||||
okHttpClientForMoney = { OkHttpClient() },
|
||||
cashuMintTransport = OkHttpMintTransport { OkHttpClient() },
|
||||
otsResolverBuilder = { EmptyOtsResolverBuilder.build() },
|
||||
cache = LocalCache,
|
||||
client = client,
|
||||
scope = scope,
|
||||
appVersion = "test",
|
||||
encryptionKeyCache = EncryptionKeyCache(),
|
||||
saveSettings = {},
|
||||
marmotNotifier = { MarmotGroupNotifier.None },
|
||||
nip46Consent = Nip46ConsentPrompter.Unanswered,
|
||||
geohashIdentityStore = InMemoryGeohashIdentityStore(),
|
||||
marmotStreamTransportFactory = ::defaultMarmotStreamTransport,
|
||||
)
|
||||
|
||||
val db =
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ import androidx.compose.ui.layout.positionInRoot
|
||||
import androidx.compose.ui.test.junit4.v2.createComposeRule
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.mediaSizingModifier
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.audioSquare
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
|
||||
+3
-9
@@ -20,13 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.components
|
||||
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TranslationPlatform
|
||||
|
||||
/**
|
||||
* No translation service in this flavor, so no note is ever translated and the copy-text
|
||||
* menus never need to offer a "Copy Translated" option.
|
||||
*/
|
||||
fun cachedTranslation(
|
||||
content: String,
|
||||
accountViewModel: AccountViewModel,
|
||||
): String? = null
|
||||
/** No translation service in this flavor, so the content is always its own "translation". */
|
||||
val FlavorTranslationPlatform: TranslationPlatform = TranslationPlatform.None
|
||||
@@ -23113,8 +23113,8 @@ SPLcom/vitorpamplona/amethyst/commons/model/AccentColorType;-><clinit>()V
|
||||
SPLcom/vitorpamplona/amethyst/commons/model/AccentColorType;-><init>(Ljava/lang/String;III)V
|
||||
SPLcom/vitorpamplona/amethyst/commons/model/AccentColorType;->valueOf(Ljava/lang/String;)Lcom/vitorpamplona/amethyst/commons/model/AccentColorType;
|
||||
SPLcom/vitorpamplona/amethyst/commons/model/AccentColorType;->values()[Lcom/vitorpamplona/amethyst/commons/model/AccentColorType;
|
||||
Lcom/vitorpamplona/amethyst/model/Account;
|
||||
Lcom/vitorpamplona/amethyst/model/AccountSettings;
|
||||
Lcom/vitorpamplona/amethyst/commons/model/Account;
|
||||
Lcom/vitorpamplona/amethyst/commons/model/AccountSettings;
|
||||
Lcom/vitorpamplona/amethyst/model/AntiSpamFilter;
|
||||
SPLcom/vitorpamplona/amethyst/model/AntiSpamFilter;-><clinit>()V
|
||||
SPLcom/vitorpamplona/amethyst/model/AntiSpamFilter;-><init>()V
|
||||
@@ -23944,7 +23944,7 @@ SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatche
|
||||
Lcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1;-><init>(Lcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher;Lkotlin/coroutines/Continuation;)V
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1;->invokeSuspend$lambda$0(Lcom/vitorpamplona/amethyst/model/Account;Lcom/vitorpamplona/amethyst/model/Account;)Z
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1;->invokeSuspend$lambda$0(Lcom/vitorpamplona/amethyst/commons/model/Account;Lcom/vitorpamplona/amethyst/commons/model/Account;)Z
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
Lcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$$ExternalSyntheticLambda0;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$$ExternalSyntheticLambda0;-><init>()V
|
||||
@@ -23952,7 +23952,7 @@ SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatche
|
||||
Lcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$2;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$2;-><init>(Lcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher;Lkotlin/coroutines/Continuation;)V
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$2;->create(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$2;->invoke(Lcom/vitorpamplona/amethyst/model/Account;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$2;->invoke(Lcom/vitorpamplona/amethyst/commons/model/Account;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$2;->invoke(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
|
||||
SPLcom/vitorpamplona/amethyst/service/notifications/NwcPaymentNotificationWatcher$start$1$2;->invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
|
||||
Lcom/vitorpamplona/amethyst/service/notifications/PokeyReceiver;
|
||||
@@ -24802,7 +24802,7 @@ Lcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;-><clinit>()V
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;-><init>(Lcom/vitorpamplona/amethyst/model/accountsCache/AccountCacheState;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lcom/vitorpamplona/amethyst/LocalPreferences;Lkotlinx/coroutines/CoroutineScope;)V
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;->getAccountContent()Lkotlinx/coroutines/flow/StateFlow;
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;->loggedInAccount()Lcom/vitorpamplona/amethyst/model/Account;
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;->loggedInAccount()Lcom/vitorpamplona/amethyst/commons/model/Account;
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;->loginWithDefaultAccount$default(Lcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;->loginWithDefaultAccount(Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
SPLcom/vitorpamplona/amethyst/ui/screen/AccountSessionManager;->loginWithDefaultAccountIfLoggedOff()V
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.os.Build
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.service.http.HttpClientEnvironment
|
||||
import com.vitorpamplona.amethyst.commons.service.http.MediaCallEventListener
|
||||
import com.vitorpamplona.amethyst.commons.util.AndroidDebugFlag
|
||||
import com.vitorpamplona.amethyst.favorites.WebShortcuts
|
||||
import com.vitorpamplona.amethyst.napplet.WebAppNetworkRegistry
|
||||
import com.vitorpamplona.amethyst.napplet.WebSitePermissionRegistry
|
||||
@@ -114,6 +115,9 @@ class Amethyst : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// Before anything logs: shared code's debug-only logging (commons `logTime`) reads this
|
||||
// instead of the app's BuildConfig, in both processes.
|
||||
AndroidDebugFlag.enabled = isDebug
|
||||
Log.d("AmethystApp") { "onCreate $this" }
|
||||
|
||||
// Application.onCreate runs in every process. The sandboxed napplet host
|
||||
|
||||
@@ -28,6 +28,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip11RelayInfo.Nip11RelayInformation
|
||||
import com.vitorpamplona.quartz.nip57Zaps.validate.LnurlEndpointCache
|
||||
import com.vitorpamplona.quartz.nip57Zaps.validate.LnurlEndpointInfo
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -56,8 +57,11 @@ class AmethystLocalCacheHost(
|
||||
|
||||
override fun relaySelfPubKey(relay: NormalizedRelayUrl): HexKey? = modules.nip11Cache.getFromCache(relay).self
|
||||
|
||||
// The zap path's LNURL cache is a quartz-side singleton the outbound-zap resolver
|
||||
// fills; it is jvmAndroid-only, which is why the cache reads it through here.
|
||||
override fun relayInfo(relay: NormalizedRelayUrl): Nip11RelayInformation = modules.nip11Cache.getFromCache(relay)
|
||||
|
||||
override val nip11Cache get() = modules.nip11Cache
|
||||
|
||||
// The zap path's LNURL cache is a quartz-side singleton the outbound-zap resolver fills.
|
||||
override fun lnurlEndpoint(lnurlpUrl: String): LnurlEndpointInfo? = LnurlEndpointCache.get(lnurlpUrl)
|
||||
|
||||
override fun assertNotMainThread() = checkNotInMainThread()
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import androidx.core.content.edit
|
||||
import com.vitorpamplona.amethyst.commons.model.GeohashIdentityStore
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.GeohashIdentitySecrets
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.readLegacyGeohashIdentity
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
|
||||
/**
|
||||
* The account's geohash identity in [accountSecretsStore], copied on first read out of the
|
||||
* encrypted SharedPreferences file older versions wrote, and mirrored back there until
|
||||
* [LocalPreferences.LEGACY_WRITES_RETIRED] (so a rollback keeps the seed and the nickname).
|
||||
*/
|
||||
class AndroidGeohashIdentityStore(
|
||||
private val pubKeyHex: HexKey,
|
||||
) : GeohashIdentityStore {
|
||||
/**
|
||||
* The npub the current store is keyed by.
|
||||
*
|
||||
* The legacy file is keyed by the pubkey *hex* — the old code passed
|
||||
* `signer.pubKey` where every other caller passes an npub, so the identity
|
||||
* lived in `secret_keeper_<hex>`, a different file from the account's own
|
||||
* `secret_keeper_<npub>`. The copy below reads that file and writes the
|
||||
* npub-keyed store, which is what folds this orphan back in with the rest.
|
||||
*/
|
||||
private val npub by lazy { pubKeyHex.hexToByteArray().toNpub() }
|
||||
|
||||
/**
|
||||
* What `secret_keeper_<pubkey hex>` holds.
|
||||
*
|
||||
* Only called when the store has nothing yet: opening this file creates it,
|
||||
* so reading it unconditionally would resurrect it after the cleanup has
|
||||
* deleted it. Touches disk; callers are off the main thread.
|
||||
*/
|
||||
private fun legacy(): GeohashIdentitySecrets = readLegacyGeohashIdentity(LegacySharedPreferences(Amethyst.instance.encryptedStorage(pubKeyHex)))
|
||||
|
||||
override suspend fun read(): GeohashIdentitySecrets = accountSecretsStore.readGeohashIdentity(npub) { legacy() }
|
||||
|
||||
override suspend fun write(value: GeohashIdentitySecrets) = accountSecretsStore.mirrorGeohashIdentity(npub, value)
|
||||
|
||||
// Mirrored, not moved: the legacy file stays readable until the legacy writes are retired
|
||||
// app-wide. Gated on the same switch as every other mirror.
|
||||
override fun mirrorLegacyNickname(nickname: String) {
|
||||
if (!LocalPreferences.LEGACY_WRITES_RETIRED) {
|
||||
Amethyst.instance.encryptedStorage(pubKeyHex).edit { putString(PREF_NICKNAME, nickname) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun mirrorLegacyDeviceSeed(seedHex: String) {
|
||||
if (!LocalPreferences.LEGACY_WRITES_RETIRED) {
|
||||
Amethyst.instance.encryptedStorage(pubKeyHex).edit { putString(PREF_KEY, seedHex) }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_KEY = "geohash_chat_device_seed"
|
||||
private const val PREF_NICKNAME = "geohash_chat_nickname"
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import com.vitorpamplona.amethyst.commons.browser.BrowserIconRegistry
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.DataStoreNostrSignerPermissionStore
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.nip46.DataStoreNip46ClientStore
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteAppsRegistry
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.NoteState
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
@@ -53,6 +54,7 @@ import com.vitorpamplona.amethyst.commons.napplet.permissions.NappletPermissionL
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.BlockedRelayFilteringClient
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.diagnostics.BootRelayDiagnostics
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.RelaySubscriptionsCoordinator
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.speedLogger.RelaySpeedLogger
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relays.health.TorCircuitHealthTracker
|
||||
@@ -63,6 +65,8 @@ import com.vitorpamplona.amethyst.commons.robohash.CachedRobohash
|
||||
import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStore
|
||||
import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostWorkGate
|
||||
import com.vitorpamplona.amethyst.commons.service.connectivity.ConnectivityStatus
|
||||
import com.vitorpamplona.amethyst.commons.service.georelay.GeoRelayCsvLoader
|
||||
import com.vitorpamplona.amethyst.commons.service.georelay.GeohashRelays
|
||||
import com.vitorpamplona.amethyst.commons.service.http.BlossomReadAuthInterceptor
|
||||
import com.vitorpamplona.amethyst.commons.service.http.BlossomReadAuthTokenProvider
|
||||
import com.vitorpamplona.amethyst.commons.service.http.DualHttpClientManager
|
||||
@@ -78,7 +82,7 @@ import com.vitorpamplona.amethyst.commons.service.pow.PoWPublishQueue
|
||||
import com.vitorpamplona.amethyst.commons.state.UiSettingsState
|
||||
import com.vitorpamplona.amethyst.commons.tor.TorRelayState
|
||||
import com.vitorpamplona.amethyst.commons.tor.TorSettings
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.Nip46ConsentBridge
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||
import com.vitorpamplona.amethyst.model.nip60Cashu.CashuPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences
|
||||
@@ -115,7 +119,6 @@ import com.vitorpamplona.amethyst.service.relayClient.CacheClientConnector
|
||||
import com.vitorpamplona.amethyst.service.relayClient.RelayProxyClientConnector
|
||||
import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.AuthCoordinator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.notifyCommand.model.NotifyCoordinator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountSubscriptionRegistry
|
||||
import com.vitorpamplona.amethyst.service.resourceusage.BatteryDrainSampler
|
||||
import com.vitorpamplona.amethyst.service.resourceusage.ForegroundTimeIntegrator
|
||||
@@ -199,6 +202,7 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
@@ -240,6 +244,13 @@ class AppModules(
|
||||
private val _trimLevelEvents = MutableSharedFlow<Int>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
val trimLevelEvents = _trimLevelEvents.asSharedFlow()
|
||||
|
||||
/**
|
||||
* Fires when memory is tight enough that feeds should drop the strong Note references they
|
||||
* hold: the process is on the system LRU list, the strongest level the OS still delivers
|
||||
* since API 34, or the heap watchdog raised the same level itself.
|
||||
*/
|
||||
val memoryPressureEvents: Flow<Unit> = trimLevelEvents.filter { it >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND }.map { }
|
||||
|
||||
/**
|
||||
* The app-wide DataStore files — the ones that belong to the install rather
|
||||
* than to an account. [AccountPreferenceStores] is the same idea keyed by
|
||||
@@ -769,6 +780,8 @@ class AppModules(
|
||||
// event, which is why this sits next to the cache rather than in a later init block.
|
||||
init {
|
||||
cache.appHost = AmethystLocalCacheHost(this, isDebug)
|
||||
// Geohash chats route through the live georelays directory, fetched over this app's clients.
|
||||
GeohashRelays.liveRelayLoader = { GeoRelayCsvLoader { okHttpClients.getHttpClient(false) }.fetch() }
|
||||
}
|
||||
|
||||
// NIP-BC onchain zap verification backend. Wired up once at app init so
|
||||
@@ -1035,6 +1048,13 @@ class AppModules(
|
||||
otsResolverBuilder = { otsResolverBuilder.build() },
|
||||
cache = cache,
|
||||
client = client,
|
||||
appVersion = BuildConfig.VERSION_NAME,
|
||||
encryptionKeyCache = keyCache,
|
||||
saveSettings = { LocalPreferences.saveToEncryptedStorage(it) },
|
||||
// A provider: notificationDispatcher is declared further down this class.
|
||||
marmotNotifier = { notificationDispatcher },
|
||||
nip46Consent = Nip46ConsentBridge,
|
||||
geohashIdentityStore = { AndroidGeohashIdentityStore(it) },
|
||||
rootFilesDir = { appContext.filesDir },
|
||||
powQueue = { powPublishQueue },
|
||||
meterSigner = { MeteringNostrSigner(it, resourceUsage) },
|
||||
|
||||
@@ -32,8 +32,6 @@ import com.vitorpamplona.quartz.utils.Log
|
||||
import com.vitorpamplona.quartz.utils.LogLevel
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.measureTimedValue
|
||||
|
||||
@Suppress("SENSELESS_COMPARISON")
|
||||
val isDebug = BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "benchmark"
|
||||
@@ -236,36 +234,6 @@ fun debugState(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> logTime(
|
||||
debugMessage: String,
|
||||
minToReportMs: Int = 1,
|
||||
block: () -> T,
|
||||
): T =
|
||||
if (isDebug) {
|
||||
val (result, elapsed) = measureTimedValue(block)
|
||||
if (elapsed.inWholeMilliseconds > minToReportMs) {
|
||||
Log.d("DEBUG-TIME") { "${elapsed.toString(DurationUnit.MILLISECONDS, 3).padStart(12)}: $debugMessage" }
|
||||
}
|
||||
result
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
|
||||
inline fun <T> logTime(
|
||||
debugMessage: (T) -> String,
|
||||
minToReportMs: Int = 1,
|
||||
block: () -> T,
|
||||
): T =
|
||||
if (isDebug) {
|
||||
val (result, elapsed) = measureTimedValue(block)
|
||||
if (elapsed.inWholeMilliseconds > minToReportMs) {
|
||||
Log.d("DEBUG-TIME") { "${elapsed.toString(DurationUnit.MILLISECONDS, 3).padStart(12)}: ${debugMessage(result)}" }
|
||||
}
|
||||
result
|
||||
} else {
|
||||
block()
|
||||
}
|
||||
|
||||
fun debug(
|
||||
tag: String,
|
||||
debugMessage: String,
|
||||
|
||||
@@ -25,6 +25,7 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.core.content.edit
|
||||
import com.vitorpamplona.amethyst.commons.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.HomeFeedType
|
||||
import com.vitorpamplona.amethyst.commons.model.UiSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.chats.ChatFeedType
|
||||
@@ -60,7 +61,6 @@ import com.vitorpamplona.amethyst.commons.model.preferences.readLegacyAccountSec
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.readLegacyGeohashIdentity
|
||||
import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.backups.BackupConflictStorage
|
||||
import com.vitorpamplona.amethyst.model.nip60Cashu.CashuPreferences
|
||||
import com.vitorpamplona.amethyst.model.preferences.UiSharedPreferences
|
||||
@@ -329,7 +329,7 @@ object LocalPreferences {
|
||||
* `amethyst/plans/2026-09-23-encrypted-storage-retirement.md`.
|
||||
*
|
||||
* `internal` rather than private because the mirror is not all in this
|
||||
* file: [com.vitorpamplona.amethyst.model.GeohashChatIdentityState] writes
|
||||
* file: [com.vitorpamplona.amethyst.commons.model.GeohashChatIdentityState] writes
|
||||
* the location-chat identity into its own legacy file and reads this to
|
||||
* know when to stop. Private, it would have kept writing after the flip
|
||||
* and the switch would only half work.
|
||||
|
||||
+6
-8
@@ -18,7 +18,7 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nip46Signer
|
||||
package com.vitorpamplona.amethyst.connectedApps.consent
|
||||
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46PermissionAuthorizer
|
||||
@@ -26,14 +26,12 @@ import com.vitorpamplona.amethyst.commons.connectedApps.signers.AppConnectResult
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerOp
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.SignerOpGrant
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip46Signer.Nip46ConsentPrompter
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_allow_always_for
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_decrypt_failed
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_remote_app
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConnectCoordinator
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConnectInfo
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConsentCoordinator
|
||||
import com.vitorpamplona.amethyst.napplet.label
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
@@ -53,7 +51,7 @@ import kotlinx.coroutines.withTimeoutOrNull
|
||||
* Runs only in the main process (the signer never runs in `:napplet`), so [Amethyst.instance] is set;
|
||||
* the coordinators launch their Activity from the application context.
|
||||
*/
|
||||
object Nip46ConsentBridge {
|
||||
object Nip46ConsentBridge : Nip46ConsentPrompter {
|
||||
/**
|
||||
* Upper bound on how long a consent prompt may block the signer's single-consumer loop. A user who
|
||||
* ignores the dialog eventually fails the request closed (deny / declined) instead of wedging the
|
||||
@@ -62,7 +60,7 @@ object Nip46ConsentBridge {
|
||||
private const val CONSENT_TIMEOUT_MS = 120_000L
|
||||
|
||||
/** First-connect consent: show the app's self-declared identity and let the user pick a trust level. */
|
||||
suspend fun requestConnect(
|
||||
override suspend fun requestConnect(
|
||||
coordinate: String,
|
||||
clientPubKey: HexKey,
|
||||
request: BunkerRequestConnect,
|
||||
@@ -96,7 +94,7 @@ object Nip46ConsentBridge {
|
||||
* [requestedOps] so the user gives informed consent before those ops are pre-granted. Returns the
|
||||
* user's [AppConnectResult] (or [AppConnectResult.Cancelled] if the prompt is never answered).
|
||||
*/
|
||||
suspend fun requestNostrConnectConsent(
|
||||
override suspend fun requestNostrConnectConsent(
|
||||
coordinate: String,
|
||||
name: String?,
|
||||
url: String?,
|
||||
@@ -133,7 +131,7 @@ object Nip46ConsentBridge {
|
||||
* user approves — and it is bounded by [Nip46ConsentInfoBuilder.DECRYPT_PREVIEW_TIMEOUT_MS] so a slow or failing signer
|
||||
* degrades to an explanatory message instead of hanging or blanking the prompt.
|
||||
*/
|
||||
suspend fun requestOp(
|
||||
override suspend fun requestOp(
|
||||
coordinate: String,
|
||||
clientPubKey: HexKey,
|
||||
op: NostrSignerOp,
|
||||
+1
-2
@@ -18,12 +18,11 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nip46Signer
|
||||
package com.vitorpamplona.amethyst.connectedApps.consent
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46PermissionAuthorizer.Companion.decryptCounterparty
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46PermissionAuthorizer.Companion.toNarrowSignerOp
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerOp
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConsentInfo
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
|
||||
+2
-2
@@ -95,12 +95,12 @@ import com.vitorpamplona.amethyst.commons.resources.nip46_signer_batch_select_no
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_batch_signing_as
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_batch_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_messages_with
|
||||
import com.vitorpamplona.amethyst.commons.service.call.CallSessionBridge
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.EmptyNav
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.service.call.CallSessionBridge
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.RumorAssembler
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
|
||||
/**
|
||||
* Pre-fetches the addressable events behind the user's favorited [FavoriteApp.NostrApp]s (nSites /
|
||||
@@ -41,7 +41,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
*
|
||||
* This subscribes each favorited coordinate to the shared
|
||||
* [EventFinder][com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderFilterAssembler]
|
||||
* — the same lifecycle-aware loader [observeNote][com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNote]
|
||||
* — the same lifecycle-aware loader [observeNote][com.vitorpamplona.amethyst.commons.relayClient.event.observeNote]
|
||||
* uses — so the manifests fetch (via the author's outbox relays) as soon as the launcher opens and are
|
||||
* already in [LocalCache] by the time the user taps. The loader drops each coordinate from its filter
|
||||
* once the event arrives, so this is a one-shot fetch, not a standing feed.
|
||||
|
||||
+48
-7
@@ -26,21 +26,28 @@ import com.vitorpamplona.amethyst.commons.connectedApps.nip46.InMemoryNip46Clien
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.nip46.Nip46ClientStore
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.InMemoryNostrSignerPermissionStore
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionStore
|
||||
import com.vitorpamplona.amethyst.commons.cordn.KeyStoreCordnBlobCipher
|
||||
import com.vitorpamplona.amethyst.commons.marmot.EncryptedKeyPackageBundleStore
|
||||
import com.vitorpamplona.amethyst.commons.marmot.EncryptedMarmotMessageStore
|
||||
import com.vitorpamplona.amethyst.commons.marmot.EncryptedMlsGroupStateStore
|
||||
import com.vitorpamplona.amethyst.commons.marmot.EncryptedPublishObligationStore
|
||||
import com.vitorpamplona.amethyst.commons.marmot.InMemoryMlsGroupStateStore
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.commons.model.GeohashIdentityStore
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.location.LocationResult
|
||||
import com.vitorpamplona.amethyst.commons.model.marmot.AndroidIngestDedupStore
|
||||
import com.vitorpamplona.amethyst.commons.model.marmot.AndroidPushStateStore
|
||||
import com.vitorpamplona.amethyst.commons.model.marmot.MarmotGroupNotifier
|
||||
import com.vitorpamplona.amethyst.commons.model.nip46Signer.Nip46ConsentPrompter
|
||||
import com.vitorpamplona.amethyst.commons.model.preferences.AppPreferenceStores
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.nip47WalletConnect.NWCPaymentFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.DataStoreRelayAuthPermissionStore
|
||||
import com.vitorpamplona.amethyst.commons.service.http.EncryptionKeyCache
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PoWPublishQueue
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.marmotquic.QuicAgentTextStreamTransport
|
||||
import com.vitorpamplona.quartz.marmot.appComponents.agentTextStream.transport.MarmotQuicTransport
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
@@ -48,9 +55,11 @@ import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
|
||||
import com.vitorpamplona.quartz.nip55AndroidSigner.client.NostrSignerExternal
|
||||
import com.vitorpamplona.quartz.nip60Cashu.mintApi.OkHttpMintTransport
|
||||
import com.vitorpamplona.quartz.nip89AppHandlers.clientTag.NostrSignerWithClientTag
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import com.vitorpamplona.quartz.utils.cache.LargeCache
|
||||
import com.vitorpamplona.quic.tls.JdkCertificateValidator
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -59,18 +68,31 @@ import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import okhttp3.OkHttpClient
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import java.io.File
|
||||
|
||||
class AccountCacheState(
|
||||
val geolocationFlow: () -> StateFlow<LocationState.LocationResult>,
|
||||
val geolocationFlow: () -> StateFlow<LocationResult>,
|
||||
val nwcFilterAssembler: () -> NWCPaymentFilterAssembler,
|
||||
val cashuMintDirectoryFilterAssembler: () -> com.vitorpamplona.amethyst.commons.relayClient.assemblers.CashuMintDirectoryFilterAssembler,
|
||||
val okHttpClientForMoney: (String) -> okhttp3.OkHttpClient,
|
||||
val okHttpClientForMoney: (String) -> OkHttpClient,
|
||||
val contentResolverFn: () -> ContentResolver,
|
||||
val otsResolverBuilder: () -> OtsResolver,
|
||||
val cache: LocalCache,
|
||||
val client: INostrClient,
|
||||
/** The running app's version name, handed to every [Account] it builds. */
|
||||
val appVersion: String,
|
||||
/** App-wide media decryption keys, shared by every [Account]. */
|
||||
val encryptionKeyCache: EncryptionKeyCache,
|
||||
/** Persists an account's settings (the app's encrypted storage). */
|
||||
val saveSettings: suspend (AccountSettings) -> Unit,
|
||||
/** Announces Marmot Welcomes and group messages; shared by every [Account]. */
|
||||
val marmotNotifier: () -> MarmotGroupNotifier,
|
||||
/** The NIP-46 consent dialogs, shared by every [Account]. */
|
||||
val nip46Consent: Nip46ConsentPrompter,
|
||||
/** Builds the store for one account's geohash-chat identity, keyed by its pubkey. */
|
||||
val geohashIdentityStore: (HexKey) -> GeohashIdentityStore,
|
||||
val rootFilesDir: () -> File = { File("") },
|
||||
val powQueue: () -> PoWPublishQueue? = { null },
|
||||
/** Optional resource-ledger wrapper applied to every account signer (see MeteringNostrSigner). */
|
||||
@@ -341,10 +363,18 @@ class AccountCacheState(
|
||||
geolocationFlow = geolocationFlow,
|
||||
nwcFilterAssembler = nwcFilterAssembler,
|
||||
cashuMintDirectoryFilterAssembler = cashuMintDirectoryFilterAssembler,
|
||||
okHttpClientForMoney = okHttpClientForMoney,
|
||||
cashuMintTransport = OkHttpMintTransport(okHttpClientForMoney),
|
||||
otsResolverBuilder = otsResolverBuilder,
|
||||
cache = cache,
|
||||
client = client,
|
||||
appVersion = appVersion,
|
||||
encryptionKeyCache = encryptionKeyCache,
|
||||
saveSettings = saveSettings,
|
||||
marmotNotifier = marmotNotifier,
|
||||
nip46Consent = nip46Consent,
|
||||
geohashIdentityStore = geohashIdentityStore(signer.pubKey),
|
||||
marmotStreamTransportFactory = ::defaultMarmotStreamTransport,
|
||||
cordnBlobCipher = { KeyStoreCordnBlobCipher() },
|
||||
scope =
|
||||
CoroutineScope(
|
||||
Dispatchers.IO +
|
||||
@@ -356,7 +386,7 @@ class AccountCacheState(
|
||||
// The same per-account directory the Marmot stores use. cordn
|
||||
// scopes itself further by coordinator underneath it, because a
|
||||
// gid is unique only within one (spec/00.md §4).
|
||||
cordnFilesDir = accountDir,
|
||||
cordnFilesDir = accountDir.toOkioPath(),
|
||||
mlsGroupStateStore = mlsStore,
|
||||
marmotMessageStore = marmotMessageStore,
|
||||
marmotKeyPackageStore = marmotKeyPackageStore,
|
||||
@@ -392,3 +422,14 @@ class AccountCacheState(
|
||||
const val CLIENT_TAG_NAME = "Amethyst"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The app's raw-QUIC transport for Marmot agent text stream previews (`transports/quic.md`).
|
||||
* Preview brokers are commonly self-signed and the binding expects that; the platform trust
|
||||
* store is still the default answer, and a deployment that pins does it here.
|
||||
*/
|
||||
fun defaultMarmotStreamTransport(scope: CoroutineScope): MarmotQuicTransport =
|
||||
QuicAgentTextStreamTransport(
|
||||
parentScope = scope,
|
||||
certificateValidator = JdkCertificateValidator(),
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.model.cordn
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||
import com.vitorpamplona.quartz.cordn.appEncryptedMedia.CordnBlobUpload
|
||||
import com.vitorpamplona.quartz.cordn.appEncryptedMedia.CordnEncryptedMedia
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nip11RelayInfo
|
||||
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.commons.relays.nip11RelayInfo.isRelaySignedRelayGroup
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
|
||||
/**
|
||||
* Whether [relay]'s cached NIP-11 document advertises support for [nip] (as a decimal string, e.g.
|
||||
* "29"). Reads only the in-memory cache — it never blocks on a network fetch — so it returns false
|
||||
* for a relay whose NIP-11 hasn't been loaded yet. Callers that need the answer to become true must
|
||||
* warm the document first (e.g. `loadRelayInfo`), then re-evaluate once it resolves.
|
||||
*/
|
||||
fun relayAdvertisesNip(
|
||||
relay: NormalizedRelayUrl,
|
||||
nip: String,
|
||||
): Boolean =
|
||||
Amethyst.instance.nip11Cache
|
||||
.getFromCache(relay)
|
||||
.supported_nips
|
||||
?.any { it == nip } == true
|
||||
|
||||
/** NIP-29 (relay-based groups): the relay must run it for its groups to be real. */
|
||||
fun relayAdvertisesNip29(relay: NormalizedRelayUrl): Boolean = relayAdvertisesNip(relay, "29")
|
||||
|
||||
/** [isRelaySignedRelayGroup] reading the host relay's cached NIP-11 doc (for non-Compose callers). */
|
||||
fun isRelaySignedRelayGroup(channel: RelayGroupChannel): Boolean = isRelaySignedRelayGroup(channel, Amethyst.instance.nip11Cache.getFromCache(channel.groupId.relayUrl))
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.nipB7Blossom
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.NoteState
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.DEFAULT_MEDIA_SERVERS
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent
|
||||
import com.vitorpamplona.quartz.utils.Rfc3986
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
class BlossomServerListState(
|
||||
val signer: NostrSigner,
|
||||
val cache: ICacheProvider,
|
||||
val scope: CoroutineScope,
|
||||
val settings: AccountSettings,
|
||||
) {
|
||||
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
|
||||
val blossomListNote = cache.getOrCreateAddressableNote(getBlossomServersAddress())
|
||||
|
||||
fun getBlossomServersAddress() = BlossomServersEvent.createAddress(signer.pubKey)
|
||||
|
||||
fun getBlossomServersListFlow(): StateFlow<NoteState> = blossomListNote.flow().metadata.stateFlow
|
||||
|
||||
fun getBlossomServersList(): BlossomServersEvent? = blossomListNote.event as? BlossomServersEvent
|
||||
|
||||
fun normalizeServers(note: Note): List<String> {
|
||||
val event = note.event as? BlossomServersEvent
|
||||
return event?.servers() ?: emptyList()
|
||||
}
|
||||
|
||||
fun host(url: String): String =
|
||||
try {
|
||||
Rfc3986.host(url).removePrefix("cdn.").removePrefix("blossom.")
|
||||
} catch (e: Exception) {
|
||||
url.removePrefix("cdn.").removePrefix("blossom.")
|
||||
}
|
||||
|
||||
val flow =
|
||||
getBlossomServersListFlow()
|
||||
.map {
|
||||
normalizeServers(it.note)
|
||||
}.flowOn(Dispatchers.IO)
|
||||
.stateIn(
|
||||
scope,
|
||||
SharingStarted.Eagerly,
|
||||
emptyList(),
|
||||
)
|
||||
|
||||
fun mergeServerList(blossom: List<String>?): List<ServerName> = blossom?.map { ServerName(host(it), it, ServerType.Blossom) }?.ifEmpty { DEFAULT_MEDIA_SERVERS } ?: DEFAULT_MEDIA_SERVERS
|
||||
|
||||
val hostNameFlow: StateFlow<List<ServerName>> =
|
||||
flow
|
||||
.map { blossoms ->
|
||||
mergeServerList(blossoms)
|
||||
}.onStart {
|
||||
emit(mergeServerList(flow.value))
|
||||
}.onEach { servers ->
|
||||
resetTargetOrNull(flow.value, servers, settings.defaultFileServer)?.let {
|
||||
settings.changeDefaultFileServer(it)
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
.stateIn(
|
||||
scope,
|
||||
SharingStarted.Eagerly,
|
||||
DEFAULT_MEDIA_SERVERS,
|
||||
)
|
||||
|
||||
suspend fun saveBlossomServersList(servers: List<String>): BlossomServersEvent {
|
||||
val serverList = getBlossomServersList()
|
||||
|
||||
return if (serverList != null && serverList.tags.isNotEmpty()) {
|
||||
BlossomServersEvent.updateRelayList(
|
||||
earlierVersion = serverList,
|
||||
servers = servers,
|
||||
signer = signer,
|
||||
)
|
||||
} else {
|
||||
BlossomServersEvent.createFromScratch(
|
||||
relays = servers,
|
||||
signer = signer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createBlossomUploadAuth(
|
||||
hash: HexKey,
|
||||
size: Long,
|
||||
alt: String,
|
||||
servers: List<String> = emptyList(),
|
||||
): BlossomAuthorizationEvent = BlossomAuthorizationEvent.createUploadAuth(hash, size, alt, signer, servers)
|
||||
|
||||
suspend fun createBlossomMediaAuth(
|
||||
hash: HexKey,
|
||||
size: Long,
|
||||
alt: String,
|
||||
servers: List<String> = emptyList(),
|
||||
): BlossomAuthorizationEvent = BlossomAuthorizationEvent.createMediaAuth(hash, size, alt, signer, servers)
|
||||
|
||||
suspend fun createBlossomDeleteAuth(
|
||||
hash: HexKey,
|
||||
alt: String,
|
||||
servers: List<String> = emptyList(),
|
||||
): BlossomAuthorizationEvent = BlossomAuthorizationEvent.createDeleteAuth(hash, alt, signer, servers)
|
||||
|
||||
suspend fun createBlossomListAuth(
|
||||
alt: String,
|
||||
servers: List<String> = emptyList(),
|
||||
): BlossomAuthorizationEvent = BlossomAuthorizationEvent.createListAuth(signer, alt, servers)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether the persisted default file server must be reset, and to what.
|
||||
*
|
||||
* Returns the new default server, or `null` when no change should happen.
|
||||
*
|
||||
* The guard on [rawList] being non-empty is what prevents the startup race: before the user's
|
||||
* [BlossomServersEvent] (kind 10063) loads from cache/relay, [rawList] is empty and [merged] is the
|
||||
* transient [DEFAULT_MEDIA_SERVERS] fallback. Resetting against that fallback would clobber the
|
||||
* locally-saved pick on every launch. Only reset once a real, loaded list is in hand and it no
|
||||
* longer contains the current pick (e.g. the user removed it from their list).
|
||||
*/
|
||||
fun resetTargetOrNull(
|
||||
rawList: List<String>,
|
||||
merged: List<ServerName>,
|
||||
current: ServerName,
|
||||
): ServerName? =
|
||||
if (rawList.isNotEmpty() && merged.none { it == current }) {
|
||||
merged.firstOrNull() ?: DEFAULT_MEDIA_SERVERS[0]
|
||||
} else {
|
||||
null
|
||||
}
|
||||
+1
-1
@@ -20,8 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.model.torState
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.tor.TorRelayState
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.model.zap
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.commons.model.MIN_ONCHAIN_ZAP_SATS
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip60Cashu.CashuWalletState
|
||||
import com.vitorpamplona.amethyst.commons.model.payments.PayToRailMatcher
|
||||
import com.vitorpamplona.amethyst.commons.model.payments.PaymentTargetTypes
|
||||
import com.vitorpamplona.amethyst.model.MIN_ONCHAIN_ZAP_SATS
|
||||
import com.vitorpamplona.amethyst.model.nip60Cashu.CashuWalletState
|
||||
import com.vitorpamplona.amethyst.service.payments.PayToAppAvailability
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip57Zaps.splits.BaseZapSplitSetup
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.browser.BrowserSitePermission
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionLedger
|
||||
import com.vitorpamplona.amethyst.commons.favorites.FavoriteApp
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletBroker
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletCapability
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletIdentity
|
||||
@@ -44,7 +45,6 @@ import com.vitorpamplona.amethyst.commons.napplet.NappletRequestRouter
|
||||
import com.vitorpamplona.amethyst.commons.napplet.protocol.NappletProtocolJson
|
||||
import com.vitorpamplona.amethyst.commons.napplet.protocol.NappletResponse
|
||||
import com.vitorpamplona.amethyst.favorites.WebShortcuts
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.napplet.gateways.AccountNappletGateways
|
||||
import com.vitorpamplona.amethyst.napplethost.NappletIpc
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
|
||||
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.napplet
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.browser.OmniboxInput
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletCapability
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletIdentity
|
||||
import com.vitorpamplona.amethyst.commons.napplet.protocol.NappletRequest
|
||||
@@ -63,7 +64,6 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_consent_upload
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_fallback_title
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadPluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
|
||||
import com.vitorpamplona.quartz.nip01Core.core.fastForEach
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
|
||||
+1
-1
@@ -20,9 +20,9 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletRelayCleartext
|
||||
import com.vitorpamplona.amethyst.commons.napplet.protocol.NappletProtocolJson
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.reqs.SubscriptionListener
|
||||
|
||||
@@ -38,9 +38,9 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_op_encrypt
|
||||
import com.vitorpamplona.amethyst.commons.resources.napplet_op_sign_kind_named
|
||||
import com.vitorpamplona.amethyst.commons.resources.nip46_signer_allow_always_for
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.screen.loggedIn.relays.kindNameFor
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConnectInfo
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConsentInfo
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.kindNameFor
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.napplet.gateways
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip51Lists.muteList.tags.UserTag
|
||||
import com.vitorpamplona.quartz.nip57Zaps.ZapReceiptEvent
|
||||
|
||||
+1
-1
@@ -30,6 +30,7 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrConnectPrompt
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerConsentPrompt
|
||||
import com.vitorpamplona.amethyst.commons.connectedApps.signers.NostrSignerPermissionLedger
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletBroker
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletConsentPrompt
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletIdentityGateway
|
||||
@@ -51,7 +52,6 @@ import com.vitorpamplona.amethyst.commons.resources.napplet_cap_notify
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConnectCoordinator
|
||||
import com.vitorpamplona.amethyst.connectedApps.consent.SignerConsentCoordinator
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.napplet.NappletConsentCoordinator
|
||||
import com.vitorpamplona.amethyst.napplet.NappletConsentSummary
|
||||
import com.vitorpamplona.amethyst.napplet.buildConnectInfo
|
||||
|
||||
+1
-1
@@ -21,9 +21,9 @@
|
||||
package com.vitorpamplona.amethyst.napplet.gateways
|
||||
|
||||
import android.util.Base64
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletResource
|
||||
import com.vitorpamplona.amethyst.commons.napplet.NappletResourceResult
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.napplet.NappletNetworkRegistry
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service
|
||||
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.quartz.experimental.clink.client.OfferClient
|
||||
import com.vitorpamplona.quartz.experimental.clink.offers.OfferEvent
|
||||
import com.vitorpamplona.quartz.experimental.clink.offers.OfferResponse
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ import androidx.core.content.ContextCompat
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.nipACWebRtcCalls.CallState
|
||||
import com.vitorpamplona.amethyst.commons.service.call.CallSessionBridge
|
||||
import com.vitorpamplona.amethyst.ui.call.CallActivity
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.call
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.vitorpamplona.amethyst.commons.service.call.CallSessionBridge
|
||||
import com.vitorpamplona.amethyst.service.call.notification.CallNotifier
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.call
|
||||
|
||||
import com.vitorpamplona.amethyst.model.CallTurnServer
|
||||
import com.vitorpamplona.amethyst.commons.model.CallTurnServer
|
||||
import org.webrtc.PeerConnection
|
||||
|
||||
object IceServerConfig {
|
||||
|
||||
+2
-2
@@ -44,11 +44,11 @@ import com.vitorpamplona.amethyst.commons.resources.crashreport_found_send
|
||||
import com.vitorpamplona.amethyst.commons.resources.would_you_like_to_send_the_recent_crash_report_to_amethyst_in_a_dm_no_personal_information_will_be_shared
|
||||
import com.vitorpamplona.amethyst.commons.service.crashreports.DEV_REPORT_PUBKEY
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeToMessage
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size16dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeToMessage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
+1
-1
@@ -22,8 +22,8 @@ package com.vitorpamplona.amethyst.service.eventCache
|
||||
|
||||
import android.content.ComponentCallbacks2
|
||||
import com.vitorpamplona.amethyst.AccountInfo
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip19Bech32.decodePublicKeyAsHexOrNull
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
+1
-11
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.service.location
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import com.vitorpamplona.amethyst.commons.model.location.LocationResult
|
||||
import com.vitorpamplona.quartz.experimental.bitchat.geohash.GeohashChannelLevel
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHash
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeohashPrecision
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -100,16 +100,6 @@ class LocationState(
|
||||
const val SUBSCRIPTION_STOP_TIMEOUT_MS: Long = 5_000L
|
||||
}
|
||||
|
||||
sealed class LocationResult {
|
||||
data class Success(
|
||||
val geoHash: GeoHash,
|
||||
) : LocationResult()
|
||||
|
||||
object LackPermission : LocationResult()
|
||||
|
||||
object Loading : LocationResult()
|
||||
}
|
||||
|
||||
private enum class Gate { NoPermission, Paused, Listen }
|
||||
|
||||
private var hasLocationPermission = MutableStateFlow(false)
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service.notifications
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.model.accountsCache.AccountCacheState
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountSubscriptionRegistry
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
@@ -58,9 +58,9 @@ import kotlinx.coroutines.launch
|
||||
* and expects the one they land on to be current, so all of them keep their own notifications,
|
||||
* DMs and gift wraps live. This costs nothing once the app is away — it ends with the screen.
|
||||
* - **While the app is away: only the accounts that opted in**, via
|
||||
* [com.vitorpamplona.amethyst.model.AccountSettings.alwaysOnNotificationService] ("Keep this
|
||||
* [com.vitorpamplona.amethyst.commons.model.AccountSettings.alwaysOnNotificationService] ("Keep this
|
||||
* account active in the background") or their NIP-46 signer toggle
|
||||
* ([com.vitorpamplona.amethyst.model.AccountSettings.nip46SignerEnabled]).
|
||||
* ([com.vitorpamplona.amethyst.commons.model.AccountSettings.nip46SignerEnabled]).
|
||||
*
|
||||
* That is what the setting's name promises, and for a while it did not hold: participation gated
|
||||
* subscriptions everywhere, so an account you had not opted in for showed no notifications even
|
||||
|
||||
+2
-2
@@ -30,17 +30,18 @@ import coil3.request.ImageRequest
|
||||
import coil3.request.allowHardware
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.isMutedPublicChatMessage
|
||||
import com.vitorpamplona.amethyst.commons.nipACWebRtcCalls.CallManager
|
||||
import com.vitorpamplona.amethyst.commons.notifications.dal.NotificationFeedFilter
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_chess_challenge_accepted
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_chess_your_turn
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_poll_channel_message
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.call.notification.CallNotifier
|
||||
import com.vitorpamplona.amethyst.service.notifications.renderers.ArticleNotification
|
||||
import com.vitorpamplona.amethyst.service.notifications.renderers.BadgeNotification
|
||||
@@ -57,7 +58,6 @@ import com.vitorpamplona.amethyst.service.notifications.renderers.RepostNotifica
|
||||
import com.vitorpamplona.amethyst.service.notifications.renderers.ZapNotification
|
||||
import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.ScreenAuthAccount
|
||||
import com.vitorpamplona.amethyst.ui.MainActivity
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.dal.NotificationFeedFilter
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageV2Event
|
||||
import com.vitorpamplona.quartz.experimental.notifications.wake.WakeUpEvent
|
||||
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
|
||||
|
||||
+7
-6
@@ -22,10 +22,11 @@ package com.vitorpamplona.amethyst.service.notifications
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.marmot.MarmotGroupNotifier
|
||||
import com.vitorpamplona.amethyst.commons.notifications.dal.NotificationFeedFilter
|
||||
import com.vitorpamplona.amethyst.service.notifications.renderers.BuzzDmNotification
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.dal.NotificationFeedFilter
|
||||
import com.vitorpamplona.quartz.buzz.stream.StreamMessageV2Event
|
||||
import com.vitorpamplona.quartz.experimental.notifications.wake.WakeUpEvent
|
||||
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
|
||||
@@ -96,7 +97,7 @@ class NotificationDispatcher(
|
||||
private val scope: CoroutineScope,
|
||||
/** Forwarded to [EventNotificationConsumer]: reports wakelock held-time to the resource-usage ledger. */
|
||||
onWakeLockHeld: ((heldMs: Long) -> Unit)? = null,
|
||||
) {
|
||||
) : MarmotGroupNotifier {
|
||||
companion object {
|
||||
private const val TAG = "NotificationDispatcher"
|
||||
|
||||
@@ -306,7 +307,7 @@ class NotificationDispatcher(
|
||||
* routing. Called from processMarmotWelcomeFlow once MLS group join
|
||||
* succeeds — at which point we know which account the invite was for.
|
||||
*/
|
||||
suspend fun notifyWelcome(
|
||||
override suspend fun notifyWelcome(
|
||||
event: WelcomeEvent,
|
||||
account: Account,
|
||||
) {
|
||||
@@ -322,10 +323,10 @@ class NotificationDispatcher(
|
||||
* Direct-invocation entry point for Marmot kind:445 group messages.
|
||||
* Bypasses the cache-observer path because GroupEvents are routed by
|
||||
* the `h` tag (nostr_group_id), not by `p` tag. Called from
|
||||
* [com.vitorpamplona.amethyst.ui.screen.loggedIn.GroupEventHandler]
|
||||
* [com.vitorpamplona.amethyst.commons.model.GroupEventHandler]
|
||||
* once the MLS-decrypted inner event has been parsed and indexed.
|
||||
*/
|
||||
suspend fun notifyGroupMessage(
|
||||
override suspend fun notifyGroupMessage(
|
||||
innerEvent: ChatEvent,
|
||||
nostrGroupId: String,
|
||||
account: Account,
|
||||
|
||||
+1
-1
@@ -23,11 +23,11 @@ package com.vitorpamplona.amethyst.service.notifications
|
||||
import android.content.Context
|
||||
import android.os.PowerManager
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.event.EventFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.UserFinderQueryState
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.ScreenAuthAccount
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
+1
-1
@@ -23,8 +23,8 @@ package com.vitorpamplona.amethyst.service.notifications
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip19Bech32.toNpub
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.renderers.NwcPaymentNotifier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -36,7 +36,7 @@ import kotlinx.coroutines.launch
|
||||
* account's connected NWC wallets.
|
||||
*
|
||||
* The relay subscription that receives these events is NOT here — it lives in
|
||||
* [com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.nip47WalletConnect.NwcNotificationsEoseManager],
|
||||
* [com.vitorpamplona.amethyst.commons.relayClient.reqCommand.account.nip47WalletConnect.NwcNotificationsEoseManager],
|
||||
* grouped with the account's always-on zap/notification inbox subscriptions so it
|
||||
* shares their lifecycle (open while logged in, warm in the background). That
|
||||
* manager decrypts each notification and publishes non-zap payments to
|
||||
|
||||
+1
-1
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.AccountInfo
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.commons.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.isDebug
|
||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
+1
-1
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_articles_channel_message
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_articles_channel_message_highlight
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_badges_channel_message
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_badges_channel_message_from
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationRoutes
|
||||
|
||||
+1
-1
@@ -21,13 +21,13 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip29RelayGroups.RelayGroupChannel
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_new_message
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationRoutes
|
||||
|
||||
+1
-1
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_chess_channel_name
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.isDebug
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationRoutes
|
||||
|
||||
+1
-1
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_code_channel_message_issue
|
||||
@@ -39,7 +40,6 @@ import com.vitorpamplona.amethyst.commons.resources.app_notification_code_channe
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_code_channel_message_status_draft
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_code_channel_message_status_open
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,9 +21,9 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_added_to_group
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_new_message
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationRoutes
|
||||
|
||||
+1
-1
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_media_channel_message_photo
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_media_channel_message_video
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,11 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_mentions_channel_message
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,11 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_payments_channel_message
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmount
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationRoutes
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.postStandard
|
||||
|
||||
+1
-1
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_reactions_channel_message
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_reactions_channel_message_for
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,12 +21,12 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_me
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,11 +21,11 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_reposts_channel_message
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.notifications.renderers
|
||||
|
||||
import android.content.Context
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.model.User
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
@@ -32,7 +33,6 @@ import com.vitorpamplona.amethyst.commons.resources.app_notification_zaps_channe
|
||||
import com.vitorpamplona.amethyst.commons.resources.app_notification_zaps_channel_message_from
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmount
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationCategory
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationContent
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationEnricher
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import coil3.asDrawable
|
||||
import coil3.imageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
+1
-1
@@ -46,6 +46,7 @@ import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.ContentFrame
|
||||
import androidx.media3.ui.compose.SURFACE_TYPE_TEXTURE_VIEW
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.getDialogWindow
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.BottomGradientOverlay
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.FullscreenSwipeControlsState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.FullscreenSwipeLevelIndicator
|
||||
@@ -63,7 +64,6 @@ import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.LoadedMe
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.isHlsMedia
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.AudioPlayingAnimation
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.wavefront.rememberIsAudioTrack
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
internal const val SKIP_SECONDS = 10
|
||||
internal const val SKIP_MILLIS = SKIP_SECONDS * 1000L
|
||||
|
||||
+5
-10
@@ -30,31 +30,26 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.DisplayBlurHash
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.ImageUrlWithDownloadButton
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.DownloadForOfflineIcon
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size75dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.imageModifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.videoGalleryModifier
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayBlurHash
|
||||
import com.vitorpamplona.amethyst.ui.components.ImageUrlWithDownloadButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Immutable
|
||||
class WaveformData(
|
||||
val wave: List<Float>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun VideoView(
|
||||
videoUri: String,
|
||||
|
||||
+2
-1
@@ -26,10 +26,11 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPlayerActiveMutex
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
+1
-1
@@ -40,6 +40,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.VideoPlayerAction
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.call_settings_video_quality
|
||||
import com.vitorpamplona.amethyst.commons.resources.download_to_phone
|
||||
@@ -59,7 +60,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.VolumeBottomIconSize
|
||||
import com.vitorpamplona.amethyst.model.VideoPlayerAction
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
||||
+5
-4
@@ -55,6 +55,8 @@ import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol
|
||||
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
|
||||
import com.vitorpamplona.amethyst.commons.model.VideoButtonLocation
|
||||
import com.vitorpamplona.amethyst.commons.model.VideoPlayerAction
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.captions_turn_off
|
||||
import com.vitorpamplona.amethyst.commons.resources.captions_turn_on
|
||||
@@ -70,8 +72,8 @@ import com.vitorpamplona.amethyst.commons.ui.theme.PinBottomIconSize
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size20Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size50Modifier
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.model.VideoButtonLocation
|
||||
import com.vitorpamplona.amethyst.model.VideoPlayerAction
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.cast.CastRequest
|
||||
import com.vitorpamplona.amethyst.service.cast.CastSessionState
|
||||
import com.vitorpamplona.amethyst.service.cast.resolveCastLiveness
|
||||
@@ -85,8 +87,7 @@ import com.vitorpamplona.amethyst.ui.cast.CastDevicePickerDialog
|
||||
import com.vitorpamplona.amethyst.ui.cast.rememberCastWithLocalNetworkPermission
|
||||
import com.vitorpamplona.amethyst.ui.components.ShareMediaAction
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.saveMediaToGallery
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
+1
-1
@@ -25,9 +25,9 @@ import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.commons.ui.state.produceCachedState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
+1
-1
@@ -22,8 +22,8 @@ package com.vitorpamplona.amethyst.service.playback.composable.mediaitem
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.media3.common.MediaItem
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.nip71Video.CaptionTrack
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
+1
-1
@@ -44,9 +44,9 @@ import androidx.media3.common.Tracks
|
||||
import com.vitorpamplona.amethyst.commons.audio.AudioVisualizer
|
||||
import com.vitorpamplona.amethyst.commons.audio.Spectrum
|
||||
import com.vitorpamplona.amethyst.commons.audio.VisualizerStyle
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.audio.delayedByFrames
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.playerPool.PcmTapRegistry
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
|
||||
|
||||
+4
-3
@@ -20,12 +20,13 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.playback.composable.wavefront
|
||||
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
|
||||
private const val SYNTHETIC_WAVEFORM_SAMPLES = 96
|
||||
private const val TWO_PI = (Math.PI * 2).toFloat()
|
||||
private const val TWO_PI = (PI * 2).toFloat()
|
||||
|
||||
/**
|
||||
* Builds a stable, decorative waveform from a [seed] (usually an event id). ExoPlayer drives
|
||||
@@ -57,7 +58,7 @@ fun syntheticWaveformFor(seed: String): WaveformData {
|
||||
List(SYNTHETIC_WAVEFORM_SAMPLES) { index ->
|
||||
val phase = index.toFloat() / SYNTHETIC_WAVEFORM_SAMPLES
|
||||
// Optional gentle fade so some taper at the ends, others stay even.
|
||||
val envelope = 1f - envelopeStrength * (1f - sin(phase * Math.PI).toFloat())
|
||||
val envelope = 1f - envelopeStrength * (1f - sin(phase * PI).toFloat())
|
||||
val carrier = sin(phase * TWO_PI * carrierCycles + phaseOffset) * carrierWeight
|
||||
val noise = (rng.nextFloat() - 0.5f) * 2f * noiseStrength
|
||||
((baseline + carrier + noise) * envelope).coerceIn(0.05f, 1.0f)
|
||||
|
||||
+1
-1
@@ -36,9 +36,9 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.media3.common.Player
|
||||
import com.linc.audiowaveform.infiniteLinearGradient
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.AudioWaveformReadOnly
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
|
||||
@Composable
|
||||
fun Waveform(
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ import androidx.collection.LruCache
|
||||
*
|
||||
* A URL is permanently one or the other, and the recorder overwrites with the latest verdict on
|
||||
* every timeline change, so a transient early value self-corrects. Backed by a bounded, thread-safe
|
||||
* [LruCache] (same convention as [com.vitorpamplona.amethyst.model.MediaAspectRatioCache]) so a long
|
||||
* [LruCache] (same convention as [com.vitorpamplona.amethyst.commons.model.MediaAspectRatioCache]) so a long
|
||||
* feed session over many distinct URLs can't grow it without bound — an evicted entry just relearns
|
||||
* on its next play, which is one uncached play, the same negligible cost as a process restart.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service.playback.pip
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.os.Bundle
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||
import com.vitorpamplona.quartz.utils.ensure
|
||||
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.util.Consumer
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.commons.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||
import com.vitorpamplona.amethyst.ui.components.getActivity
|
||||
|
||||
+2
-2
@@ -40,10 +40,10 @@ import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.ContentFrame
|
||||
import androidx.media3.ui.compose.state.rememberMuteButtonState
|
||||
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
||||
import com.vitorpamplona.amethyst.commons.audio.WaveformData
|
||||
import com.vitorpamplona.amethyst.commons.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.VoiceHeightModifier
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.PIP_PRESHRINK_MAX_SHORT_SIDE_PX
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.controls.constrainVideoQualityToViewport
|
||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.audio.AudioSink
|
||||
import androidx.media3.exoplayer.audio.DefaultAudioSink
|
||||
import androidx.media3.exoplayer.audio.TeeAudioProcessor
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.commons.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCache
|
||||
import com.vitorpamplona.amethyst.service.playback.playerPool.aspectRatio.AspectRatioCacher
|
||||
import com.vitorpamplona.amethyst.service.playback.playerPool.positions.CurrentPlayPositionCacher
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.service.playback.playerPool.aspectRatio
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.VideoSize
|
||||
import com.vitorpamplona.amethyst.model.MutableMediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.commons.model.MutableMediaAspectRatioCache
|
||||
|
||||
class AspectRatioCacher(
|
||||
val cache: MutableMediaAspectRatioCache,
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.pow
|
||||
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPost
|
||||
import com.vitorpamplona.amethyst.commons.scheduledposts.ScheduledPostStore
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PersistedPoWJob
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PoWJobStore
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PoWPublishQueue
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
package com.vitorpamplona.amethyst.service.pow
|
||||
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.boost
|
||||
import com.vitorpamplona.amethyst.commons.resources.post
|
||||
import com.vitorpamplona.amethyst.commons.resources.pow_kind_chat_message
|
||||
@@ -30,6 +29,7 @@ import com.vitorpamplona.amethyst.commons.resources.private_message
|
||||
import com.vitorpamplona.amethyst.commons.resources.reaction
|
||||
import com.vitorpamplona.amethyst.commons.resources.voice_post
|
||||
import com.vitorpamplona.amethyst.commons.resources.voice_reply
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.powKindLabelRes
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
@@ -39,24 +39,6 @@ import com.vitorpamplona.quartz.nip56Reports.ReportEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent
|
||||
import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
|
||||
/**
|
||||
* The one user-facing label for "what is being mined": shared by the
|
||||
* broadcast banner, the mining foreground notification, and failure toasts
|
||||
* so a job is described the same way everywhere it appears.
|
||||
*/
|
||||
fun powKindLabelRes(kind: Int): StringResource =
|
||||
when (kind) {
|
||||
ReactionEvent.KIND -> Res.string.reaction
|
||||
RepostEvent.KIND, GenericRepostEvent.KIND -> Res.string.boost
|
||||
VoiceEvent.KIND -> Res.string.voice_post
|
||||
VoiceReplyEvent.KIND -> Res.string.voice_reply
|
||||
ReportEvent.KIND -> Res.string.pow_kind_report
|
||||
GiftWrapEvent.KIND -> Res.string.private_message
|
||||
ChannelMessageEvent.KIND, LiveActivitiesChatMessageEvent.KIND -> Res.string.pow_kind_chat_message
|
||||
else -> Res.string.post
|
||||
}
|
||||
|
||||
/**
|
||||
* Android-resource twin of [powKindLabelRes], for [com.vitorpamplona.amethyst.service.pow.PowMiningForegroundService].
|
||||
|
||||
+1
-1
@@ -24,9 +24,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.AuthCoordinator
|
||||
import com.vitorpamplona.amethyst.service.relayClient.authCommand.model.ScreenAuthAccount
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun RelayAuthSubscription(accountViewModel: AccountViewModel) = RelayAuthSubscription(accountViewModel, Amethyst.instance.authCoordinator)
|
||||
|
||||
+5
-5
@@ -64,8 +64,11 @@ import androidx.compose.ui.unit.sp
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.model.cache.LocalCache
|
||||
import com.vitorpamplona.amethyst.commons.model.nip11RelayInfo.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.auth.RelayAuthPrompt
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.auth.UserAuthChoice
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.channel.observeChannel
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.AuthPurpose
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.AuthPurposeKind
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy
|
||||
@@ -98,15 +101,12 @@ import com.vitorpamplona.amethyst.commons.resources.relay_auth_why_send_dm
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_why_thread
|
||||
import com.vitorpamplona.amethyst.commons.resources.relay_auth_why_thread_with
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.commons.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.screen.LocalDisplaySettings
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.RelayIconFilter
|
||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserInfo
|
||||
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
|
||||
+1
-1
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.authCommand.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.buzz.BuzzRelayDialect
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.auth.ListWithUniqueSetCache
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.auth.RelayAuthFirstParty
|
||||
@@ -32,7 +33,6 @@ import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthDecision
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthPolicy
|
||||
import com.vitorpamplona.amethyst.commons.relayauth.RelayAuthVerdict
|
||||
import com.vitorpamplona.amethyst.isDebug
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.payment_required_title
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
|
||||
@Composable
|
||||
|
||||
+2
-2
@@ -42,13 +42,13 @@ import com.vitorpamplona.amethyst.commons.model.EmptyTagList
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.error_dialog_button_ok
|
||||
import com.vitorpamplona.amethyst.commons.resources.notify_block_relay
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.Size16dp
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.StdHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.rememberExtendedNav
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun NotifyRequestDialog(
|
||||
|
||||
+1
-1
@@ -21,8 +21,8 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.notifyCommand.model
|
||||
|
||||
import android.util.LruCache
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.notify.NotifyRequestsCache
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.RelayConnectionListener
|
||||
|
||||
+3
-1
@@ -22,8 +22,10 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.account.AccountFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.account.AccountUiQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun AccountFilterAssemblerSubscription(accountViewModel: AccountViewModel) =
|
||||
|
||||
+3
-1
@@ -22,8 +22,10 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.account.AccountForegroundFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.account.AccountUiQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
|
||||
@Composable
|
||||
fun AccountForegroundFilterAssemblerSubscription(accountViewModel: AccountViewModel) =
|
||||
|
||||
+3
-1
@@ -20,7 +20,9 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.account
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.account.AccountFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.account.AccountQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
|
||||
|
||||
+7
-5
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.channel.nip28PublicChats.filterChannelMetadataUpdatesById
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
|
||||
@@ -36,14 +36,16 @@ class ChannelMetadataWatcherSubAssembler(
|
||||
override fun updateFilter(
|
||||
key: ChannelFinderQueryState,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter> =
|
||||
if (key.channel is PublicChatChannel) {
|
||||
key.channel.relays().flatMap {
|
||||
filterChannelMetadataUpdatesById(it, listOf(key.channel), since?.get(it)?.time)
|
||||
): List<RelayBasedFilter> {
|
||||
val channel = key.channel
|
||||
return if (channel is PublicChatChannel) {
|
||||
channel.relays().flatMap {
|
||||
filterChannelMetadataUpdatesById(it, listOf(channel), since?.get(it)?.time)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only one key per channel.
|
||||
|
||||
+7
-5
@@ -24,8 +24,8 @@ import com.vitorpamplona.amethyst.commons.model.Channel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.channel.nip53LiveActivities.filterLiveStreamUpdatesByAddress
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
|
||||
@@ -40,14 +40,16 @@ class LiveActivityWatcherSubAssembly(
|
||||
override fun updateFilter(
|
||||
key: ChannelFinderQueryState,
|
||||
since: SincePerRelayMap?,
|
||||
): List<RelayBasedFilter> =
|
||||
if (key.channel is LiveActivitiesChannel) {
|
||||
key.channel.relays().flatMap {
|
||||
filterLiveStreamUpdatesByAddress(it, listOf(key.channel), since?.get(it)?.time)
|
||||
): List<RelayBasedFilter> {
|
||||
val channel = key.channel
|
||||
return if (channel is LiveActivitiesChannel) {
|
||||
channel.relays().flatMap {
|
||||
filterLiveStreamUpdatesByAddress(it, listOf(channel), since?.get(it)?.time)
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Only one key per channel.
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.commons.model.Note
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.nip47WalletConnect.NWCPaymentFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.nip47WalletConnect.NWCPaymentQueryState
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.events.NwcRequestEvent
|
||||
|
||||
@SuppressLint("StateFlowValueCalledInComposition")
|
||||
|
||||
+2
-1
@@ -21,8 +21,9 @@
|
||||
package com.vitorpamplona.amethyst.service.relayClient.searchCommand
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.searchCommand.SearchFilterAssembler
|
||||
import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.LifecycleAwareKeyDataSourceSubscription
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchBarViewModel
|
||||
|
||||
@Composable
|
||||
|
||||
+2
-2
@@ -46,10 +46,10 @@ import com.vitorpamplona.amethyst.commons.resources.resource_usage_reason_reconn
|
||||
import com.vitorpamplona.amethyst.commons.resources.resource_usage_reason_wakelock
|
||||
import com.vitorpamplona.amethyst.commons.service.crashreports.DEV_REPORT_PUBKEY
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.navigation.routes.routeToMessage
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeToMessage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
+38
-52
@@ -21,8 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.service.uploads
|
||||
|
||||
import android.media.MediaDataSource
|
||||
import com.vitorpamplona.amethyst.commons.service.image.BlurhashWrapper
|
||||
import com.vitorpamplona.amethyst.commons.service.image.ThumbhashWrapper
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.FileHeader
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
@@ -31,58 +30,45 @@ import kotlinx.coroutines.CancellationException
|
||||
import okhttp3.OkHttpClient
|
||||
import java.io.IOException
|
||||
|
||||
class FileHeader(
|
||||
val mimeType: String?,
|
||||
val hash: String,
|
||||
val size: Int,
|
||||
val dim: DimensionTag?,
|
||||
val blurHash: BlurhashWrapper?,
|
||||
val thumbHash: ThumbhashWrapper? = null,
|
||||
) {
|
||||
class UnableToDownload(
|
||||
val fileUrl: String,
|
||||
) : Exception()
|
||||
/** Downloads [fileUrl] and computes its [FileHeader] with [prepare]. */
|
||||
suspend fun FileHeader.Companion.prepare(
|
||||
fileUrl: String,
|
||||
mimeType: String?,
|
||||
dimPrecomputed: DimensionTag?,
|
||||
okHttpClient: (String) -> OkHttpClient,
|
||||
): Result<FileHeader> =
|
||||
try {
|
||||
val imageData: ImageDownloader.Blob? = ImageDownloader().waitAndGetImage(fileUrl, okHttpClient)
|
||||
|
||||
companion object {
|
||||
suspend fun prepare(
|
||||
fileUrl: String,
|
||||
mimeType: String?,
|
||||
dimPrecomputed: DimensionTag?,
|
||||
okHttpClient: (String) -> OkHttpClient,
|
||||
): Result<FileHeader> =
|
||||
try {
|
||||
val imageData: ImageDownloader.Blob? = ImageDownloader().waitAndGetImage(fileUrl, okHttpClient)
|
||||
|
||||
if (imageData != null) {
|
||||
prepare(imageData.bytes, mimeType ?: imageData.contentType, dimPrecomputed)
|
||||
} else {
|
||||
Result.failure(UnableToDownload(fileUrl))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("ImageDownload") { "Couldn't download image from server: ${e.message}" }
|
||||
Result.failure(e)
|
||||
}
|
||||
|
||||
fun prepare(
|
||||
data: ByteArray,
|
||||
mimeType: String?,
|
||||
dimPrecomputed: DimensionTag?,
|
||||
): Result<FileHeader> =
|
||||
try {
|
||||
val hash = sha256(data).toHexKey()
|
||||
val size = data.size
|
||||
|
||||
val preview = PreviewMetadataCalculator.computeFromBytes(data, mimeType, dimPrecomputed)
|
||||
|
||||
Result.success(FileHeader(mimeType, hash, size, preview.dim, preview.blurhash, preview.thumbhash))
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("ImageDownload") { "Couldn't convert image in to File Header: ${e.message}" }
|
||||
Result.failure(e)
|
||||
}
|
||||
if (imageData != null) {
|
||||
prepare(imageData.bytes, mimeType ?: imageData.contentType, dimPrecomputed)
|
||||
} else {
|
||||
Result.failure(FileHeader.UnableToDownload(fileUrl))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("ImageDownload") { "Couldn't download image from server: ${e.message}" }
|
||||
Result.failure(e)
|
||||
}
|
||||
|
||||
/** Hashes [data] and decodes its dimensions and preview hashes (Android media APIs). */
|
||||
fun FileHeader.Companion.prepare(
|
||||
data: ByteArray,
|
||||
mimeType: String?,
|
||||
dimPrecomputed: DimensionTag?,
|
||||
): Result<FileHeader> =
|
||||
try {
|
||||
val hash = sha256(data).toHexKey()
|
||||
val size = data.size
|
||||
|
||||
val preview = PreviewMetadataCalculator.computeFromBytes(data, mimeType, dimPrecomputed)
|
||||
|
||||
Result.success(FileHeader(mimeType, hash, size, preview.dim, preview.blurhash, preview.thumbhash))
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
Log.e("ImageDownload") { "Couldn't convert image in to File Header: ${e.message}" }
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
class ByteArrayMediaDataSource(
|
||||
var imageData: ByteArray,
|
||||
+1
-1
@@ -25,8 +25,8 @@ import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMediaProcessing
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||
|
||||
+2
-1
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.uploads
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerType
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.blossomUploadOrder
|
||||
@@ -39,7 +40,7 @@ import com.vitorpamplona.amethyst.commons.resources.server_did_not_provide_a_url
|
||||
import com.vitorpamplona.amethyst.commons.resources.upload_cancelled
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.BlossomClient
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.BlossomPaymentException
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.FileHeader
|
||||
import com.vitorpamplona.amethyst.service.uploads.UploadingState.UploadingFinalState
|
||||
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||
import com.vitorpamplona.amethyst.service.uploads.nip96.Nip96Uploader
|
||||
|
||||
+1
-1
@@ -22,8 +22,8 @@ package com.vitorpamplona.amethyst.service.uploads.blossom
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.BlossomClient
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.uploads.blossom
|
||||
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.quartz.lightning.LnInvoiceUtil
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.rpc.PayInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nipB7Blossom.BlossomPaymentProof
|
||||
|
||||
+1
-1
@@ -28,9 +28,9 @@ import android.webkit.MimeTypeMap
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.failed_to_delete_with_message
|
||||
import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_to_server_with_message
|
||||
import com.vitorpamplona.amethyst.commons.service.HttpStatusMessages
|
||||
import com.vitorpamplona.amethyst.commons.service.upload.BlossomPaymentException
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.service.HttpStatusMessages
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.uploads.MediaUploadResult
|
||||
import com.vitorpamplona.amethyst.service.uploads.PreviewMetadataCalculator
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user