mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
refactor(avm): factory lambda, shared pow label and icon types, HTTP helpers out
- LoggedInPage builds AccountViewModel through a viewModel { }
initializer; the Class<T>-based Factory, the VM's last JVM-only
member, is gone.
- powKindLabelRes (compose resources only) moves to commonsUI; its
R.string twin for the mining foreground service stays in the app.
- MarmotGroupIconUpload and MarmotGroupIconChange are plain data and
move to commons/marmot, out of the Android uploader's file.
- urlPreview and checkVideoIsOnline go through the app's OkHttp clients
and become app-side extensions beside saveMediaToGallery, with the
same call shape.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7FuNBSKiyVecARSoE4B9P
This commit is contained in:
@@ -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].
|
||||
|
||||
@@ -100,6 +100,7 @@ import com.vitorpamplona.amethyst.commons.service.broadcast.BroadcastStatus
|
||||
import com.vitorpamplona.amethyst.commons.service.broadcast.RelayResult
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PoWEstimator
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PoWJobState
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.powKindLabelRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.pluralStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.ThemeComparisonColumn
|
||||
@@ -107,7 +108,6 @@ import com.vitorpamplona.amethyst.commons.ui.theme.allGoodColor
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.warningColor
|
||||
import com.vitorpamplona.amethyst.service.pow.deviceHashesPerSecond
|
||||
import com.vitorpamplona.amethyst.service.pow.formatTimeLeft
|
||||
import com.vitorpamplona.amethyst.service.pow.powKindLabelRes
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.HalfVertPadding
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.urlPreview
|
||||
|
||||
@Composable
|
||||
fun LoadUrlPreview(
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.commons.richtext.UrlParser
|
||||
import com.vitorpamplona.amethyst.model.MediaAspectRatioCache
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.urlPreview
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ import com.vitorpamplona.amethyst.ui.components.DisplayUrlWithLoadingSymbol
|
||||
import com.vitorpamplona.amethyst.ui.components.WaitAndDisplay
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.urlPreview
|
||||
|
||||
@Composable
|
||||
fun PreviewUrl(
|
||||
|
||||
+2
-44
@@ -25,12 +25,12 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.commons.audio.VisualizerStyle
|
||||
import com.vitorpamplona.amethyst.commons.cashu.ops.describeMintError
|
||||
import com.vitorpamplona.amethyst.commons.feeds.CardFeedState
|
||||
import com.vitorpamplona.amethyst.commons.feeds.FeedState
|
||||
import com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconChange
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.commons.model.CombinedZap
|
||||
@@ -88,13 +88,12 @@ import com.vitorpamplona.amethyst.commons.resources.signer_not_found_exception_d
|
||||
import com.vitorpamplona.amethyst.commons.resources.unauthorized_exception
|
||||
import com.vitorpamplona.amethyst.commons.resources.unauthorized_exception_description
|
||||
import com.vitorpamplona.amethyst.commons.resources.user_x_does_not_have_a_lightning_address_setup_to_receive_sats
|
||||
import com.vitorpamplona.amethyst.commons.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.commons.service.broadcast.BroadcastTracker
|
||||
import com.vitorpamplona.amethyst.commons.service.http.IRoleBasedHttpClientBuilder
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.PoWCategory
|
||||
import com.vitorpamplona.amethyst.commons.service.pow.powKindLabelRes
|
||||
import com.vitorpamplona.amethyst.commons.state.UiSettingsState
|
||||
import com.vitorpamplona.amethyst.commons.tor.TorSettingsFlow
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.toasts.ToastManager
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.state.GenericBaseCache
|
||||
@@ -104,16 +103,12 @@ import com.vitorpamplona.amethyst.commons.util.logTime
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmount
|
||||
import com.vitorpamplona.amethyst.commons.util.showAmountInteger
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModelHost
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.model.privacyOptions.RoleBasedHttpClientBuilder
|
||||
import com.vitorpamplona.amethyst.service.ClinkDebitPayer
|
||||
import com.vitorpamplona.amethyst.service.V4VPaymentHandler
|
||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||
import com.vitorpamplona.amethyst.service.cashu.melt.MeltProcessor
|
||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||
import com.vitorpamplona.amethyst.service.pow.powKindLabelRes
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconChange
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.markRoomNoteAsRead
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.rowHasUnread
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSync
|
||||
@@ -143,7 +138,6 @@ import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.EncryptedDmEvent
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.INip05Client
|
||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
|
||||
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||
import com.vitorpamplona.quartz.nip17Dm.base.NIP17Group
|
||||
@@ -2156,15 +2150,6 @@ class AccountViewModel(
|
||||
account.deleteStatus(LocalCache.getOrCreateAddressableNote(address))
|
||||
}
|
||||
|
||||
fun urlPreview(
|
||||
url: String,
|
||||
onResult: suspend (UrlPreviewState) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
UrlCachedPreviewer.previewInfo(url, httpClientBuilder::okHttpClientForPreview, onResult)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadReactionTo(note: Note?): String? {
|
||||
if (note == null) return null
|
||||
|
||||
@@ -2262,11 +2247,6 @@ class AccountViewModel(
|
||||
.mapNotNull { hex -> checkGetOrCreateUser(hex) }
|
||||
.sortedByDescending { account.isKnown(it) }
|
||||
|
||||
suspend fun checkVideoIsOnline(videoUrl: String): Boolean =
|
||||
withContext(Dispatchers.IO) {
|
||||
OnlineChecker.isOnline(videoUrl, httpClientBuilder::okHttpClientForVideo)
|
||||
}
|
||||
|
||||
fun loadAndMarkAsRead(
|
||||
routeForLastRead: String,
|
||||
createdAt: Long?,
|
||||
@@ -2313,28 +2293,6 @@ class AccountViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
val account: Account,
|
||||
val settings: UiSettingsState,
|
||||
val torSettings: TorSettingsFlow,
|
||||
val dataSources: RelaySubscriptionsCoordinator,
|
||||
val okHttpClient: RoleBasedHttpClientBuilder,
|
||||
val nip05ClientBuilder: () -> Nip05Client,
|
||||
val host: AccountViewModelHost,
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
||||
AccountViewModel(
|
||||
account,
|
||||
settings,
|
||||
torSettings,
|
||||
dataSources,
|
||||
okHttpClient,
|
||||
nip05ClientBuilder,
|
||||
host,
|
||||
) as T
|
||||
}
|
||||
|
||||
init {
|
||||
Log.d("AccountViewModel", "Init")
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
|
||||
+22
-3
@@ -27,17 +27,36 @@ import android.os.Looper
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconUpload
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.failed_to_save_the_video
|
||||
import com.vitorpamplona.amethyst.commons.resources.video_saved_to_the_gallery
|
||||
import com.vitorpamplona.amethyst.commons.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.commons.ui.components.UrlPreviewState
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconUpload
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconUploader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
// Account actions that read a content Uri or write to the device's media store, which only the
|
||||
// Android app can do. They stay out of AccountViewModel so the view model does not need Android.
|
||||
// Account actions that need Android (a content Uri, the media store) or the JVM HTTP stack the
|
||||
// app's OkHttp clients come from. They stay out of AccountViewModel so the view model needs neither.
|
||||
|
||||
fun AccountViewModel.urlPreview(
|
||||
url: String,
|
||||
onResult: suspend (UrlPreviewState) -> Unit,
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
UrlCachedPreviewer.previewInfo(url, httpClientBuilder::okHttpClientForPreview, onResult)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun AccountViewModel.checkVideoIsOnline(videoUrl: String): Boolean =
|
||||
withContext(Dispatchers.IO) {
|
||||
OnlineChecker.isOnline(videoUrl, httpClientBuilder::okHttpClientForVideo)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt + upload a picked image as a group avatar (canonical `marmot-group-image-v1` scheme).
|
||||
|
||||
+11
-13
@@ -69,19 +69,17 @@ fun LoggedInPage(
|
||||
accountSessionManager: AccountSessionManager,
|
||||
) {
|
||||
val accountViewModel: AccountViewModel =
|
||||
viewModel(
|
||||
key = "AccountViewModel",
|
||||
factory =
|
||||
AccountViewModel.Factory(
|
||||
account = account,
|
||||
settings = Amethyst.instance.uiState,
|
||||
torSettings = Amethyst.instance.torPrefs.value,
|
||||
dataSources = Amethyst.instance.sources,
|
||||
okHttpClient = Amethyst.instance.roleBasedHttpClientBuilder,
|
||||
nip05ClientBuilder = { Amethyst.instance.nip05Client },
|
||||
host = AndroidAccountViewModelHost(Amethyst.instance),
|
||||
),
|
||||
)
|
||||
viewModel(key = "AccountViewModel") {
|
||||
AccountViewModel(
|
||||
account = account,
|
||||
settings = Amethyst.instance.uiState,
|
||||
torSettings = Amethyst.instance.torPrefs.value,
|
||||
dataSources = Amethyst.instance.sources,
|
||||
httpClientBuilder = Amethyst.instance.roleBasedHttpClientBuilder,
|
||||
nip05ClientBuilder = { Amethyst.instance.nip05Client },
|
||||
host = AndroidAccountViewModelHost(Amethyst.instance),
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
accountViewModel.firstRoute = route
|
||||
|
||||
+1
-1
@@ -44,6 +44,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconChange
|
||||
import com.vitorpamplona.amethyst.commons.marmot.ui.MarmotRetentionChoice
|
||||
import com.vitorpamplona.amethyst.commons.marmot.ui.MarmotRetentionPicker
|
||||
import com.vitorpamplona.amethyst.commons.model.navigation.Route
|
||||
@@ -65,7 +66,6 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.CreatingTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconChange
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.uploadMarmotGroupIcon
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.utils.RandomInstance
|
||||
|
||||
+1
-1
@@ -43,6 +43,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconChange
|
||||
import com.vitorpamplona.amethyst.commons.resources.Res
|
||||
import com.vitorpamplona.amethyst.commons.resources.description
|
||||
import com.vitorpamplona.amethyst.commons.resources.marmot_avatar_url
|
||||
@@ -63,7 +64,6 @@ import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.ActionTopBar
|
||||
import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconChange
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.uploadMarmotGroupIcon
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
* icon is [removeRequested], or the group's current (decrypted) avatar. Tapping the
|
||||
* avatar opens the system photo picker; a text button below removes the current icon.
|
||||
* All selection state is hoisted so the parent screen can turn it into a
|
||||
* [com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send.MarmotGroupIconChange]
|
||||
* [com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconChange]
|
||||
* at save time.
|
||||
*/
|
||||
@Composable
|
||||
|
||||
+1
-42
@@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.marmotGroup.send
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.vitorpamplona.amethyst.commons.marmot.MarmotGroupIconUpload
|
||||
import com.vitorpamplona.amethyst.commons.model.Account
|
||||
import com.vitorpamplona.amethyst.commons.model.mediaServers.ServerName
|
||||
import com.vitorpamplona.amethyst.commons.ui.loadStringRes
|
||||
@@ -31,53 +32,11 @@ import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
||||
import com.vitorpamplona.amethyst.service.uploads.UploadingState
|
||||
import com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupImageCipher
|
||||
import com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupImageEncryption
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* The parameters produced by encrypting + uploading a new Marmot group avatar,
|
||||
* ready to be folded into a [com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupData]
|
||||
* via [com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupData.withImage].
|
||||
*/
|
||||
class MarmotGroupIconUpload(
|
||||
/** SHA-256 (hex) of the encrypted blob = its Blossom content hash. */
|
||||
val imageHash: HexKey,
|
||||
/** 32-byte HKDF seed for the image AEAD key (MIP-01 v2). */
|
||||
val imageKey: ByteArray,
|
||||
/** 12-byte nonce. */
|
||||
val imageNonce: ByteArray,
|
||||
/** 32-byte HKDF seed for the Blossom-auth keypair (MIP-01 v2). */
|
||||
val imageUploadKey: ByteArray,
|
||||
/**
|
||||
* Media type of the DECRYPTED image.
|
||||
*
|
||||
* MIP-01's blob never carried one, but the current profile's `0x8002`
|
||||
* component requires it on a present image — and it is bound into the
|
||||
* AEAD's AAD there, so a receiver cannot be steered into decoding the
|
||||
* plaintext as a different type than the uploader meant.
|
||||
*/
|
||||
val mediaType: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* How a metadata update should treat the group icon.
|
||||
*/
|
||||
sealed class MarmotGroupIconChange {
|
||||
/** Leave the existing icon (if any) untouched. */
|
||||
data object Keep : MarmotGroupIconChange()
|
||||
|
||||
/** Remove the current icon. */
|
||||
data object Clear : MarmotGroupIconChange()
|
||||
|
||||
/** Replace the icon with a freshly-uploaded one. */
|
||||
class Set(
|
||||
val upload: MarmotGroupIconUpload,
|
||||
) : MarmotGroupIconChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a picked image with the MIP-01 v2 scheme (see [MarmotGroupImageEncryption])
|
||||
* and uploads the ciphertext to Blossom, signing the upload authorization with the
|
||||
|
||||
+1
@@ -105,6 +105,7 @@ import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.backups.BackupConflictCards
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.checkVideoIsOnline
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.geohash.NewGeoPostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.NewHashtagPostButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription
|
||||
|
||||
+1
@@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
import com.vitorpamplona.amethyst.commons.service.OnlineChecker
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.checkVideoIsOnline
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
+1
@@ -107,6 +107,7 @@ import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold
|
||||
import com.vitorpamplona.amethyst.ui.navigation.bottombars.AppBottomBar
|
||||
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.urlPreview
|
||||
import com.vitorpamplona.quartz.nipB0WebBookmarks.WebBookmarkEvent
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.marmot
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
/**
|
||||
* The parameters produced by encrypting + uploading a new Marmot group avatar,
|
||||
* ready to be folded into a [com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupData]
|
||||
* via [com.vitorpamplona.quartz.marmot.mip01Groups.MarmotGroupData.withImage].
|
||||
*/
|
||||
class MarmotGroupIconUpload(
|
||||
/** SHA-256 (hex) of the encrypted blob = its Blossom content hash. */
|
||||
val imageHash: HexKey,
|
||||
/** 32-byte HKDF seed for the image AEAD key (MIP-01 v2). */
|
||||
val imageKey: ByteArray,
|
||||
/** 12-byte nonce. */
|
||||
val imageNonce: ByteArray,
|
||||
/** 32-byte HKDF seed for the Blossom-auth keypair (MIP-01 v2). */
|
||||
val imageUploadKey: ByteArray,
|
||||
/**
|
||||
* Media type of the DECRYPTED image.
|
||||
*
|
||||
* MIP-01's blob never carried one, but the current profile's `0x8002`
|
||||
* component requires it on a present image — and it is bound into the
|
||||
* AEAD's AAD there, so a receiver cannot be steered into decoding the
|
||||
* plaintext as a different type than the uploader meant.
|
||||
*/
|
||||
val mediaType: String,
|
||||
)
|
||||
|
||||
/**
|
||||
* How a metadata update should treat the group icon.
|
||||
*/
|
||||
sealed class MarmotGroupIconChange {
|
||||
/** Leave the existing icon (if any) untouched. */
|
||||
data object Keep : MarmotGroupIconChange()
|
||||
|
||||
/** Remove the current icon. */
|
||||
data object Clear : MarmotGroupIconChange()
|
||||
|
||||
/** Replace the icon with a freshly-uploaded one. */
|
||||
class Set(
|
||||
val upload: MarmotGroupIconUpload,
|
||||
) : MarmotGroupIconChange()
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.commons.service.pow
|
||||
|
||||
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
|
||||
import com.vitorpamplona.amethyst.commons.resources.pow_kind_report
|
||||
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.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user