- Updates several libraries

- Reformats code to the newest Klint
- Fixes isEmpty bug on Filters
This commit is contained in:
Vitor Pamplona
2026-02-09 14:06:07 -05:00
parent 710f15f790
commit eb66182211
133 changed files with 1373 additions and 580 deletions
+1 -1
View File
@@ -11,6 +11,6 @@
<option name="languageVersion" value="2.3" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.3.0" />
<option name="version" value="2.3.10" />
</component>
</project>
@@ -898,59 +898,101 @@ object LocalCache : ILocalCache, ICacheProvider {
@Suppress("DEPRECATION")
fun computeReplyTo(event: Event): List<Note> =
when (event) {
is PollNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
is LongTextNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
is GitReplyEvent -> event.tagsWithoutCitations().filter { it != event.repository()?.toTag() }.mapNotNull { checkGetOrCreateNote(it) }
is TextNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
is CommentEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
is PollNoteEvent -> {
event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
}
is VoiceReplyEvent -> event.markedReplyTos().mapNotNull { checkGetOrCreateNote(it) }
is LongTextNoteEvent -> {
event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
}
is ChatMessageEvent -> event.taggedEvents().mapNotNull { checkGetOrCreateNote(it) }
is ChatMessageEncryptedFileHeaderEvent -> event.taggedEvents().mapNotNull { checkGetOrCreateNote(it) }
is GitReplyEvent -> {
event.tagsWithoutCitations().filter { it != event.repository()?.toTag() }.mapNotNull { checkGetOrCreateNote(it) }
}
is LnZapEvent ->
is TextNoteEvent -> {
event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
}
is CommentEvent -> {
event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
}
is VoiceReplyEvent -> {
event.markedReplyTos().mapNotNull { checkGetOrCreateNote(it) }
}
is ChatMessageEvent -> {
event.taggedEvents().mapNotNull { checkGetOrCreateNote(it) }
}
is ChatMessageEncryptedFileHeaderEvent -> {
event.taggedEvents().mapNotNull { checkGetOrCreateNote(it) }
}
is LnZapEvent -> {
event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) } +
(event.zapRequest?.taggedAddresses()?.map { getOrCreateAddressableNote(it) } ?: emptyList())
is LnZapRequestEvent ->
}
is LnZapRequestEvent -> {
event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
is BadgeProfilesEvent ->
}
is BadgeProfilesEvent -> {
event.badgeAwardEvents().mapNotNull { checkGetOrCreateNote(it) } +
event.badgeAwardDefinitions().map { getOrCreateAddressableNote(it) }
is BadgeAwardEvent -> event.awardDefinition().map { getOrCreateAddressableNote(it) }
is PrivateDmEvent -> event.taggedEvents().mapNotNull { checkGetOrCreateNote(it) }
is RepostEvent ->
}
is BadgeAwardEvent -> {
event.awardDefinition().map { getOrCreateAddressableNote(it) }
}
is PrivateDmEvent -> {
event.taggedEvents().mapNotNull { checkGetOrCreateNote(it) }
}
is RepostEvent -> {
listOfNotNull(
event.boostedEventId()?.let { checkGetOrCreateNote(it) },
event.boostedAddress()?.let { getOrCreateAddressableNote(it) },
)
is GenericRepostEvent ->
}
is GenericRepostEvent -> {
listOfNotNull(
event.boostedEventId()?.let { checkGetOrCreateNote(it) },
event.boostedAddress()?.let { getOrCreateAddressableNote(it) },
)
is CommunityPostApprovalEvent ->
}
is CommunityPostApprovalEvent -> {
event.approvedEvents().mapNotNull { checkGetOrCreateNote(it) } +
event.approvedAddresses().map { getOrCreateAddressableNote(it) }
is ReactionEvent ->
}
is ReactionEvent -> {
event.originalPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) }
is ChannelMessageEvent ->
event
.tagsWithoutCitations()
.filter { it != event.channelId() }
.mapNotNull { checkGetOrCreateNote(it) }
is LiveActivitiesChatMessageEvent ->
event
.tagsWithoutCitations()
.filter { it != event.activity()?.toTag() }
.mapNotNull { checkGetOrCreateNote(it) }
is TorrentCommentEvent ->
event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
}
else -> emptyList()
is ChannelMessageEvent -> {
event.tagsWithoutCitations().filter { it != event.channelId() }.mapNotNull { checkGetOrCreateNote(it) }
}
is LiveActivitiesChatMessageEvent -> {
event.tagsWithoutCitations().filter { it != event.activity()?.toTag() }.mapNotNull { checkGetOrCreateNote(it) }
}
is TorrentCommentEvent -> {
event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
}
else -> {
emptyList()
}
}
fun consume(
@@ -2691,27 +2733,38 @@ object LocalCache : ILocalCache, ICacheProvider {
fun consume(nip19: Entity) {
when (nip19) {
is NSec -> getOrCreateUser(nip19.toPubKeyHex())
is NPub -> getOrCreateUser(nip19.hex)
is NSec -> {
getOrCreateUser(nip19.toPubKeyHex())
}
is NPub -> {
getOrCreateUser(nip19.hex)
}
is NProfile -> {
nip19.relay.forEach { relayHint ->
relayHints.addKey(nip19.hex, relayHint)
}
getOrCreateUser(nip19.hex)
}
is NNote -> {
getOrCreateNote(nip19.hex)
}
is NEvent -> {
nip19.relay.forEach { relayHint ->
relayHints.addEvent(nip19.hex, relayHint)
}
getOrCreateNote(nip19.hex)
}
is NEmbed -> {
justConsume(nip19.event, null, false)
}
is NRelay -> {}
is NAddress -> {
val aTag = nip19.aTag()
nip19.relay.forEach { relayHint ->
@@ -2719,6 +2772,7 @@ object LocalCache : ILocalCache, ICacheProvider {
}
getOrCreateAddressableNote(nip19.address())
}
else -> { }
}
}
@@ -2966,10 +3020,7 @@ object LocalCache : ILocalCache, ICacheProvider {
is VoiceReplyEvent -> consume(event, relay, wasVerified)
is WikiNoteEvent -> consume(event, relay, wasVerified)
is PaymentTargetsEvent -> consume(event, relay, wasVerified)
else -> {
Log.w("Event Not Supported", "From ${relay?.url}: ${event.toJson()}")
false
}
else -> Log.w("Event Not Supported", "From ${relay?.url}: ${event.toJson()}").let { false }
}
} catch (e: Exception) {
if (e is CancellationException) throw e
@@ -168,6 +168,7 @@ fun parseResourceBundleOrNull(json: String): FhirElementDatabase? {
is Bundle -> {
resource.entry.associateBy { it.id }.toImmutableMap()
}
else -> {
persistentMapOf(resource.id to resource)
}
@@ -54,9 +54,7 @@ fun parseThemeType(code: Int?): ThemeType =
ThemeType.SYSTEM.screenCode -> ThemeType.SYSTEM
ThemeType.LIGHT.screenCode -> ThemeType.LIGHT
ThemeType.DARK.screenCode -> ThemeType.DARK
else -> {
ThemeType.SYSTEM
}
else -> ThemeType.SYSTEM
}
enum class ConnectivityType(
@@ -91,9 +89,7 @@ fun parseConnectivityType(code: Boolean?): ConnectivityType =
ConnectivityType.ALWAYS.prefCode -> ConnectivityType.ALWAYS
ConnectivityType.WIFI_ONLY.prefCode -> ConnectivityType.WIFI_ONLY
ConnectivityType.NEVER.prefCode -> ConnectivityType.NEVER
else -> {
ConnectivityType.ALWAYS
}
else -> ConnectivityType.ALWAYS
}
fun parseConnectivityType(screenCode: Int): ConnectivityType =
@@ -101,9 +97,7 @@ fun parseConnectivityType(screenCode: Int): ConnectivityType =
ConnectivityType.ALWAYS.screenCode -> ConnectivityType.ALWAYS
ConnectivityType.WIFI_ONLY.screenCode -> ConnectivityType.WIFI_ONLY
ConnectivityType.NEVER.screenCode -> ConnectivityType.NEVER
else -> {
ConnectivityType.ALWAYS
}
else -> ConnectivityType.ALWAYS
}
fun parseFeatureSetType(screenCode: Int): FeatureSetType =
@@ -111,18 +105,14 @@ fun parseFeatureSetType(screenCode: Int): FeatureSetType =
FeatureSetType.COMPLETE.screenCode -> FeatureSetType.COMPLETE
FeatureSetType.SIMPLIFIED.screenCode -> FeatureSetType.SIMPLIFIED
FeatureSetType.PERFORMANCE.screenCode -> FeatureSetType.PERFORMANCE
else -> {
FeatureSetType.COMPLETE
}
else -> FeatureSetType.COMPLETE
}
fun parseGalleryType(screenCode: Int): ProfileGalleryType =
when (screenCode) {
ProfileGalleryType.CLASSIC.screenCode -> ProfileGalleryType.CLASSIC
ProfileGalleryType.MODERN.screenCode -> ProfileGalleryType.MODERN
else -> {
ProfileGalleryType.CLASSIC
}
else -> ProfileGalleryType.CLASSIC
}
enum class BooleanType(
@@ -138,18 +128,14 @@ fun parseBooleanType(code: Boolean?): BooleanType =
when (code) {
BooleanType.ALWAYS.prefCode -> BooleanType.ALWAYS
BooleanType.NEVER.prefCode -> BooleanType.NEVER
else -> {
BooleanType.ALWAYS
}
else -> BooleanType.ALWAYS
}
fun parseBooleanType(screenCode: Int): BooleanType =
when (screenCode) {
BooleanType.ALWAYS.screenCode -> BooleanType.ALWAYS
BooleanType.NEVER.screenCode -> BooleanType.NEVER
else -> {
BooleanType.ALWAYS
}
else -> BooleanType.ALWAYS
}
enum class WarningType(
@@ -167,9 +153,7 @@ fun parseWarningType(screenCode: Int): WarningType =
WarningType.WARN.screenCode -> WarningType.WARN
WarningType.SHOW.screenCode -> WarningType.SHOW
WarningType.HIDE.screenCode -> WarningType.HIDE
else -> {
WarningType.WARN
}
else -> WarningType.WARN
}
fun parseWarningType(code: Boolean?): WarningType =
@@ -177,7 +161,5 @@ fun parseWarningType(code: Boolean?): WarningType =
WarningType.WARN.prefCode -> WarningType.WARN
WarningType.HIDE.prefCode -> WarningType.HIDE
WarningType.SHOW.prefCode -> WarningType.SHOW
else -> {
WarningType.WARN
}
else -> WarningType.WARN
}
@@ -68,13 +68,17 @@ class AccountCacheState(
NostrSignerInternal(accountSettings.keyPair)
} else {
when (val packageName = accountSettings.externalSignerPackageName) {
null -> NostrSignerInternal(accountSettings.keyPair)
else ->
null -> {
NostrSignerInternal(accountSettings.keyPair)
}
else -> {
NostrSignerExternal(
pubKey = accountSettings.keyPair.pubKey.toHexKey(),
packageName = packageName,
contentResolver = contentResolverFn(),
)
}
}
},
accountSettings = accountSettings,
@@ -74,7 +74,10 @@ class Nip11CachedRetriever(
val doc = relayInformationDocumentCache.get(relay)
if (doc != null) {
when (doc) {
is RetrieveResult.Success -> onInfo(doc.data)
is RetrieveResult.Success -> {
onInfo(doc.data)
}
is RetrieveResult.Loading -> {
if (doc.isValid()) {
// just wait.
@@ -82,6 +85,7 @@ class Nip11CachedRetriever(
retrieve(relay, onInfo, onError)
}
}
is RetrieveResult.Error -> {
if (doc.isValid()) {
onError(relay, doc.error, null)
@@ -89,7 +93,10 @@ class Nip11CachedRetriever(
retrieve(relay, onInfo, onError)
}
}
is RetrieveResult.Empty -> retrieve(relay, onInfo, onError)
is RetrieveResult.Empty -> {
retrieve(relay, onInfo, onError)
}
}
} else {
retrieve(relay, onInfo, onError)
@@ -64,11 +64,26 @@ class FeedTopNavFilterState(
) {
fun loadFlowsFor(listName: String): IFeedFlowsType =
when (listName) {
GLOBAL_FOLLOWS -> GlobalFeedFlow(followsRelays, proxyRelays)
ALL_FOLLOWS -> AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
ALL_USER_FOLLOWS -> AllUserFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
KIND3_FOLLOWS -> Kind3UserFollowsFeedFlow(kind3Follows, followsRelays, blockedRelays, proxyRelays)
AROUND_ME -> AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays)
GLOBAL_FOLLOWS -> {
GlobalFeedFlow(followsRelays, proxyRelays)
}
ALL_FOLLOWS -> {
AllFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
}
ALL_USER_FOLLOWS -> {
AllUserFollowsFeedFlow(allFollows, followsRelays, blockedRelays, proxyRelays)
}
KIND3_FOLLOWS -> {
Kind3UserFollowsFeedFlow(kind3Follows, followsRelays, blockedRelays, proxyRelays)
}
AROUND_ME -> {
AroundMeFeedFlow(locationFlow, followsRelays, proxyRelays)
}
else -> {
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
if (note != null) {
@@ -68,21 +68,27 @@ class NoteFeedFlow(
AuthorsByOutboxTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), blockedRelays)
}
}
is MuteListEvent -> {
MutedAuthorsByOutboxTopNavFilter(caches.muteListCache.cachedUserIdSet(noteEvent), blockedRelays)
}
is FollowListEvent -> {
AuthorsByOutboxTopNavFilter(noteEvent.followIdSet(), blockedRelays)
}
is CommunityListEvent -> {
AllCommunitiesTopNavFilter(caches.communityListCache.cachedCommunityIdSet(noteEvent), blockedRelays)
}
is HashtagListEvent -> {
HashtagTopNavFilter(caches.hashtagCache.cachedHashtags(noteEvent), outboxRelays)
}
is GeohashListEvent -> {
LocationTopNavFilter(caches.geohashCache.cachedGeohashes(noteEvent), outboxRelays)
}
is CommunityDefinitionEvent -> {
SingleCommunityTopNavFilter(
community = noteEvent.addressTag(),
@@ -91,7 +97,10 @@ class NoteFeedFlow(
blockedRelays = blockedRelays,
)
}
else -> AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays)
else -> {
AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays)
}
}
suspend fun FlowCollector<IFeedTopNavFilter>.processByOutbox(
@@ -106,21 +115,27 @@ class NoteFeedFlow(
emit(AuthorsByOutboxTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), blockedRelays))
}
}
is MuteListEvent -> {
emit(MutedAuthorsByOutboxTopNavFilter(caches.muteListCache.mutedUserIdSet(noteEvent), blockedRelays))
}
is FollowListEvent -> {
emit(AuthorsByOutboxTopNavFilter(noteEvent.followIdSet(), blockedRelays))
}
is CommunityListEvent -> {
emit(AllCommunitiesTopNavFilter(caches.communityListCache.communityIdSet(noteEvent), blockedRelays))
}
is HashtagListEvent -> {
emit(HashtagTopNavFilter(caches.hashtagCache.hashtags(noteEvent), outboxRelays))
}
is GeohashListEvent -> {
emit(LocationTopNavFilter(caches.geohashCache.geohashes(noteEvent), outboxRelays))
}
is CommunityDefinitionEvent -> {
emit(
SingleCommunityTopNavFilter(
@@ -131,10 +146,12 @@ class NoteFeedFlow(
),
)
}
else ->
else -> {
emit(
AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays),
)
}
}
}
@@ -150,21 +167,27 @@ class NoteFeedFlow(
AuthorsByProxyTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), proxyRelays)
}
}
is MuteListEvent -> {
MutedAuthorsByProxyTopNavFilter(caches.muteListCache.cachedUserIdSet(noteEvent), proxyRelays)
}
is FollowListEvent -> {
AuthorsByProxyTopNavFilter(noteEvent.followIdSet(), proxyRelays)
}
is CommunityListEvent -> {
AllCommunitiesTopNavFilter(caches.communityListCache.cachedCommunityIdSet(noteEvent), blockedRelays)
}
is HashtagListEvent -> {
HashtagTopNavFilter(caches.hashtagCache.cachedHashtags(noteEvent), proxyRelays)
}
is GeohashListEvent -> {
LocationTopNavFilter(caches.geohashCache.cachedGeohashes(noteEvent), proxyRelays)
}
is CommunityDefinitionEvent -> {
SingleCommunityTopNavFilter(
community = noteEvent.addressTag(),
@@ -173,7 +196,10 @@ class NoteFeedFlow(
blockedRelays = blockedRelays,
)
}
else -> AuthorsByProxyTopNavFilter(emptySet(), proxyRelays)
else -> {
AuthorsByProxyTopNavFilter(emptySet(), proxyRelays)
}
}
suspend fun FlowCollector<IFeedTopNavFilter>.processByProxy(
@@ -188,21 +214,27 @@ class NoteFeedFlow(
emit(AuthorsByProxyTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), proxyRelays))
}
}
is MuteListEvent -> {
emit(MutedAuthorsByProxyTopNavFilter(caches.muteListCache.mutedUserIdSet(noteEvent), proxyRelays))
}
is FollowListEvent -> {
emit(AuthorsByProxyTopNavFilter(noteEvent.followIdSet(), proxyRelays))
}
is CommunityListEvent -> {
emit(AllCommunitiesTopNavFilter(caches.communityListCache.communityIdSet(noteEvent), blockedRelays))
}
is HashtagListEvent -> {
emit(HashtagTopNavFilter(caches.hashtagCache.hashtags(noteEvent), proxyRelays))
}
is GeohashListEvent -> {
emit(LocationTopNavFilter(caches.geohashCache.geohashes(noteEvent), proxyRelays))
}
is CommunityDefinitionEvent -> {
emit(
SingleCommunityTopNavFilter(
@@ -213,10 +245,12 @@ class NoteFeedFlow(
),
)
}
else ->
else -> {
emit(
AuthorsByProxyTopNavFilter(emptySet(), proxyRelays),
)
}
}
}
@@ -25,14 +25,16 @@ import com.vitorpamplona.amethyst.commons.model.ImmutableListOfLists
fun String.isUTF16Char(pos: Int): Boolean = Character.charCount(this.codePointAt(pos)) == 2
fun String.firstFullCharOld(): String {
return when (this.length) {
0,
1,
-> return this
2,
3,
-> return if (isUTF16Char(0)) this.take(2) else this.take(1)
fun String.firstFullCharOld(): String =
when (this.length) {
0, 1 -> {
this
}
2, 3 -> {
if (isUTF16Char(0)) this.take(2) else this.take(1)
}
else -> {
val first = isUTF16Char(0)
val second = isUTF16Char(2)
@@ -45,7 +47,6 @@ fun String.firstFullCharOld(): String {
}
}
}
}
fun String.firstFullChar(): String {
var isInJoin = false
@@ -45,7 +45,6 @@ class HttpStatusMessages {
416 -> R.string.http_status_416
417 -> R.string.http_status_417
426 -> R.string.http_status_426
500 -> R.string.http_status_500
501 -> R.string.http_status_501
502 -> R.string.http_status_502
@@ -56,7 +55,6 @@ class HttpStatusMessages {
507 -> R.string.http_status_507
508 -> R.string.http_status_508
511 -> R.string.http_status_511
else -> null
}
}
@@ -97,6 +97,7 @@ class ZapPaymentHandler(
weight = setup.weight,
)
}
is ZapSplitSetup -> {
val user = LocalCache.checkGetOrCreateUser(setup.pubKeyHex)
UnverifiedZapSplitSetup(
@@ -366,13 +366,21 @@ class BroadcastTracker {
// Recalculate status
val newStatus =
when {
currentBroadcast.results.values.any { it is RelayResult.Pending || it is RelayResult.Retrying } ->
currentBroadcast.results.values.any { it is RelayResult.Pending || it is RelayResult.Retrying } -> {
BroadcastStatus.IN_PROGRESS
currentBroadcast.results.all { it.value is RelayResult.Success } ->
}
currentBroadcast.results.all { it.value is RelayResult.Success } -> {
BroadcastStatus.SUCCESS
currentBroadcast.results.none { it.value is RelayResult.Success } ->
}
currentBroadcast.results.none { it.value is RelayResult.Success } -> {
BroadcastStatus.FAILED
else -> BroadcastStatus.PARTIAL
}
else -> {
BroadcastStatus.PARTIAL
}
}
currentBroadcast.copy(status = newStatus)
}
@@ -157,6 +157,7 @@ class EventNotificationConsumer(
null
}
}
is SealedRumorEvent -> {
if (LocalCache.justConsume(event, null, false)) {
// new event
@@ -174,6 +175,7 @@ class EventNotificationConsumer(
null
}
}
else -> {
LocalCache.justConsume(event, null, false)
event
@@ -1,85 +0,0 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.service.playback.composable
import android.view.Surface
import android.view.SurfaceView
import android.view.TextureView
import androidx.annotation.IntDef
import androidx.compose.foundation.AndroidEmbeddedExternalSurface
import androidx.compose.foundation.AndroidExternalSurface
import androidx.compose.foundation.AndroidExternalSurfaceScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.media3.common.Player
/**
* Provides a dedicated drawing [Surface] for media playbacks using a [Player].
*
* The player's video output is displayed with either a [SurfaceView]/[AndroidExternalSurface] or a
* [TextureView]/[AndroidEmbeddedExternalSurface].
*
* [Player] takes care of attaching the rendered output to the [Surface] and clearing it, when it is
* destroyed.
*
* See
* [Choosing a surface type](https://developer.android.com/media/media3/ui/playerview#surfacetype)
* for more information.
*/
@Composable
fun PlayerSurface(
player: Player,
surfaceType: @SurfaceType Int = SURFACE_TYPE_SURFACE_VIEW,
modifier: Modifier = Modifier,
) {
val onSurfaceCreated: (Surface) -> Unit = { surface -> player.setVideoSurface(surface) }
val onSurfaceDestroyed: () -> Unit = { player.setVideoSurface(null) }
val onSurfaceInitialized: AndroidExternalSurfaceScope.() -> Unit = {
onSurface { surface, _, _ ->
onSurfaceCreated(surface)
surface.onDestroyed { onSurfaceDestroyed() }
}
}
when (surfaceType) {
SURFACE_TYPE_SURFACE_VIEW ->
AndroidExternalSurface(modifier = modifier, onInit = onSurfaceInitialized)
SURFACE_TYPE_TEXTURE_VIEW ->
AndroidEmbeddedExternalSurface(modifier = modifier, onInit = onSurfaceInitialized)
else -> throw IllegalArgumentException("Unrecognized surface type: $surfaceType")
}
}
/**
* The type of surface view used for media playbacks. One of [SURFACE_TYPE_SURFACE_VIEW] or
* [SURFACE_TYPE_TEXTURE_VIEW].
*/
@MustBeDocumented
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.TYPE_PARAMETER)
@IntDef(SURFACE_TYPE_SURFACE_VIEW, SURFACE_TYPE_TEXTURE_VIEW)
annotation class SurfaceType
/** Surface type equivalent to [SurfaceView] . */
const val SURFACE_TYPE_SURFACE_VIEW = 1
/** Surface type equivalent to [TextureView]. */
const val SURFACE_TYPE_TEXTURE_VIEW = 2
@@ -60,6 +60,7 @@ object BlurhashMetadataCalculator {
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, createBitmapOptions())
processImage(bitmap, dimPrecomputed)
}
isVideo(mimeType) -> {
val retriever = MediaMetadataRetriever()
try {
@@ -69,7 +70,10 @@ object BlurhashMetadataCalculator {
retriever.release()
}
}
else -> null to dimPrecomputed
else -> {
null to dimPrecomputed
}
}
fun computeFromUri(
@@ -82,11 +86,12 @@ object BlurhashMetadataCalculator {
return try {
when {
isImage(mimeType) ->
isImage(mimeType) -> {
context.contentResolver.openInputStream(uri)?.use { stream ->
val bitmap = BitmapFactory.decodeStream(stream, null, createBitmapOptions())
processImage(bitmap, dimPrecomputed)
} ?: (null to dimPrecomputed)
}
isVideo(mimeType) -> {
val retriever = MediaMetadataRetriever()
@@ -98,7 +103,9 @@ object BlurhashMetadataCalculator {
}
}
else -> null
else -> {
null
}
}
} catch (e: Exception) {
Log.w("BlurhashMetadataCalc", "Failed to compute metadata from uri", e)
@@ -60,11 +60,16 @@ class MediaCompressor {
contentType?.startsWith("video", ignoreCase = true) == true -> {
VideoCompressionHelper.compressVideo(uri, contentType, applicationContext, mediaQuality, useH265)
}
contentType?.startsWith("image", ignoreCase = true) == true &&
!contentType.contains("gif") &&
!contentType.contains("svg") ->
!contentType.contains("svg") -> {
compressImage(uri, contentType, applicationContext, mediaQuality)
else -> MediaCompressorResult(uri, contentType, null)
}
else -> {
MediaCompressorResult(uri, contentType, null)
}
}
}
@@ -150,9 +150,18 @@ fun uriToRoute(
val route =
when (nip19) {
is NPub -> Route.Profile(nip19.hex)
is NProfile -> Route.Profile(nip19.hex)
is NNote -> Route.Note(nip19.hex)
is NPub -> {
Route.Profile(nip19.hex)
}
is NProfile -> {
Route.Profile(nip19.hex)
}
is NNote -> {
Route.Note(nip19.hex)
}
is NEvent -> {
routeFor(
note = LocalCache.getOrCreateNote(nip19.hex),
@@ -182,7 +191,9 @@ fun uriToRoute(
}
}
else -> null
else -> {
null
}
}
if (route != null) {
@@ -57,8 +57,11 @@ object MediaSaverToDisk {
onError: (Throwable) -> Any?,
) = withContext(Dispatchers.IO) {
when {
videoUri.isNullOrBlank() -> return@withContext
videoUri.startsWith("file") ->
videoUri.isNullOrBlank() -> {
return@withContext
}
videoUri.startsWith("file") -> {
save(
localFile = videoUri.toUri().toFile(),
mimeType = mimeType,
@@ -66,7 +69,9 @@ object MediaSaverToDisk {
onSuccess = onSuccess,
onError = onError,
)
else ->
}
else -> {
downloadAndSave(
url = videoUri,
mimeType = mimeType,
@@ -75,6 +80,7 @@ object MediaSaverToDisk {
onSuccess = onSuccess,
onError = onError,
)
}
}
}
@@ -71,12 +71,25 @@ class NewMessageTagger(
val results = parseDirtyWordForKey(word)
when (val entity = results?.key?.entity) {
is NPub -> addUserToMentions(dao.getOrCreateUser(entity.hex))
is NProfile -> addUserToMentions(dao.getOrCreateUser(entity.hex))
is NPub -> {
addUserToMentions(dao.getOrCreateUser(entity.hex))
}
is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> addNoteToReplyTos(dao.getOrCreateNote(entity.hex))
is NEvent -> addNoteToReplyTos(dao.getOrCreateNote(entity.hex))
is NEmbed -> addNoteToReplyTos(dao.getOrCreateNote(entity.event.id))
is NProfile -> {
addUserToMentions(dao.getOrCreateUser(entity.hex))
}
is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> {
addNoteToReplyTos(dao.getOrCreateNote(entity.hex))
}
is NEvent -> {
addNoteToReplyTos(dao.getOrCreateNote(entity.hex))
}
is NEmbed -> {
addNoteToReplyTos(dao.getOrCreateNote(entity.event.id))
}
is NAddress -> {
val note = dao.getOrCreateAddressableNote(entity.address())
@@ -86,6 +99,7 @@ class NewMessageTagger(
}
is NSec -> {}
is NRelay -> {}
}
}
@@ -104,6 +118,7 @@ class NewMessageTagger(
is NPub -> {
getNostrAddress(dao.getOrCreateUser(entity.hex).toNProfile(), results.restOfWord)
}
is NProfile -> {
getNostrAddress(dao.getOrCreateUser(entity.hex).toNProfile(), results.restOfWord)
}
@@ -111,6 +126,7 @@ class NewMessageTagger(
is com.vitorpamplona.quartz.nip19Bech32.entities.NNote -> {
getNostrAddress(dao.getOrCreateNote(entity.hex).toNEvent(), results.restOfWord)
}
is NEvent -> {
getNostrAddress(dao.getOrCreateNote(entity.hex).toNEvent(), results.restOfWord)
}
@@ -306,7 +306,10 @@ class VoiceAnonymizer {
val randomShift = 0.9 + (Math.random() * 0.2)
baseFactor * randomShift
}
else -> baseFactor
else -> {
baseFactor
}
}
val totalSamples = pcmData.size
val processedSamples = ArrayList<Float>(totalSamples)
@@ -476,6 +476,7 @@ private fun RelayResultRow(
overflow = TextOverflow.Ellipsis,
)
}
is RelayResult.Timeout -> {
Text(
text = "Timeout",
@@ -483,6 +484,7 @@ private fun RelayResultRow(
color = warningColor,
)
}
is RelayResult.Retrying -> {
Text(
text = "Retrying...",
@@ -490,6 +492,7 @@ private fun RelayResultRow(
color = MaterialTheme.colorScheme.primary,
)
}
else -> {}
}
}
@@ -84,6 +84,7 @@ fun BroadcastSnackbarHost(
onViewDetails(broadcast)
}
}
SnackbarResult.Dismissed -> { /* No action */ }
}
}
@@ -104,8 +105,12 @@ private fun BroadcastSnackbarContent(snackbarData: SnackbarData) {
val containerColor =
when (broadcastVisuals?.broadcast?.status) {
BroadcastStatus.FAILED -> Color(0xFF7F1D1D) // Dark red
BroadcastStatus.PARTIAL -> Color(0xFF78350F) // Dark amber
BroadcastStatus.FAILED -> Color(0xFF7F1D1D)
// Dark red
BroadcastStatus.PARTIAL -> Color(0xFF78350F)
// Dark amber
else -> Color(0xFF1E3A5F) // Dark blue (default)
}
@@ -93,12 +93,17 @@ fun CashuPreview(
CrossfadeIfEnabled(targetState = cashuData, label = "CashuPreview", accountViewModel = accountViewModel) {
when (it) {
is GenericLoadable.Loaded<ImmutableList<CashuToken>> -> CashuPreview(it.loaded, accountViewModel)
is GenericLoadable.Error<ImmutableList<CashuToken>> ->
is GenericLoadable.Loaded<ImmutableList<CashuToken>> -> {
CashuPreview(it.loaded, accountViewModel)
}
is GenericLoadable.Error<ImmutableList<CashuToken>> -> {
Text(
text = "$cashutoken ",
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content),
)
}
else -> {}
}
}
@@ -73,11 +73,13 @@ fun LoadUrlPreviewDirect(
is UrlPreviewState.Loaded -> {
RenderLoaded(state, url, callbackUri, accountViewModel)
}
is UrlPreviewState.Loading -> {
WaitAndDisplay {
DisplayUrlWithLoadingSymbol(url)
}
}
else -> {
ClickableUrl(urlText, url)
}
@@ -87,6 +87,7 @@ fun MyAsyncImage(
}
}
}
is AsyncImagePainter.State.Error -> {
if (onError != null) {
if (ratio != null) {
@@ -102,6 +103,7 @@ fun MyAsyncImage(
ClickableUrl(urlText = imageUrl, url = imageUrl)
}
}
is AsyncImagePainter.State.Success -> {
SubcomposeAsyncImageContent(loadedImageModifier)
@@ -110,6 +112,7 @@ fun MyAsyncImage(
MediaAspectRatioCache.add(imageUrl, drawable.width, drawable.height)
}
}
else -> {}
}
}
@@ -160,7 +160,9 @@ fun RenderStrangeNamePreview() {
)
}
is RegularTextSegment -> Text(word.segmentText)
is RegularTextSegment -> {
Text(word.segmentText)
}
}
}
}
@@ -180,12 +182,21 @@ fun RenderRegularPreview() {
when (word) {
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
is EmojiSegment -> {
RenderCustomEmoji(word.segmentText, state)
}
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
is EmailSegment -> ClickableEmail(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
is EmailSegment -> {
ClickableEmail(word.segmentText)
}
is PhoneSegment -> {
ClickablePhone(word.segmentText)
}
is BechSegment -> {
CreateClickableText(
word.segmentText.substring(0, 10),
@@ -196,11 +207,19 @@ fun RenderRegularPreview() {
)
}
is HashTagSegment -> HashTag(word, EmptyNav())
is HashTagSegment -> {
HashTag(word, EmptyNav())
}
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
is RegularTextSegment -> Text(word.segmentText)
is SchemelessUrlSegment -> {
NoProtocolUrlRenderer(word)
}
is RegularTextSegment -> {
Text(word.segmentText)
}
}
}
}
@@ -219,16 +238,21 @@ fun RenderRegularPreview2() {
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
// is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, accountViewModel)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
is EmailSegment -> ClickableEmail(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
// is BechSegment -> BechLink(word.segmentText, true, backgroundColor, accountViewModel, nav)
is HashTagSegment -> HashTag(word, EmptyNav())
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
is RegularTextSegment -> Text(word.segmentText)
}
}
@@ -260,17 +284,23 @@ fun RenderRegularPreview3() {
when (word) {
// is ImageSegment -> ZoomableContentView(word.segmentText, state, accountViewModel)
is LinkSegment -> LoadUrlPreview(word.segmentText, word.segmentText, null, accountViewModel)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
// is InvoiceSegment -> MayBeInvoicePreview(word.segmentText)
// is WithdrawSegment -> MayBeWithdrawal(word.segmentText)
// is CashuSegment -> CashuPreview(word.segmentText, accountViewModel)
is EmailSegment -> ClickableEmail(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
// is BechSegment -> BechLink(word.segmentText, true, backgroundColor, accountViewModel, nav)
is HashTagSegment -> HashTag(word, EmptyNav())
// is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
// is HashIndexEventSegment -> TagLink(word, true, backgroundColorState, accountViewModel, nav)
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
is RegularTextSegment -> Text(word.segmentText)
}
}
@@ -415,23 +445,38 @@ private fun RenderWordWithoutPreview(
when (word) {
// Don't preview Images
is ImageSegment -> ClickableUrl(word.segmentText, word.segmentText)
// Don't preview Videos
is VideoSegment -> ClickableUrl(word.segmentText, word.segmentText)
is LinkSegment -> ClickableUrl(word.segmentText, word.segmentText)
is EmojiSegment -> RenderCustomEmoji(word.segmentText, state)
// Don't offer to pay invoices
is InvoiceSegment -> Text(word.segmentText)
// Don't offer to withdraw
is WithdrawSegment -> Text(word.segmentText)
is CashuSegment -> Text(word.segmentText)
is EmailSegment -> ClickableEmail(word.segmentText)
is SecretEmoji -> Text(word.segmentText)
is PhoneSegment -> ClickablePhone(word.segmentText)
is BechSegment -> BechLink(word.segmentText, false, 0, backgroundColor, accountViewModel, nav)
is HashTagSegment -> HashTag(word, nav)
is HashIndexUserSegment -> TagLink(word, accountViewModel, nav)
is HashIndexEventSegment -> TagLink(word, false, 0, backgroundColor, accountViewModel, nav)
is SchemelessUrlSegment -> NoProtocolUrlRenderer(word)
is RegularTextSegment -> Text(word.segmentText)
}
}
@@ -65,8 +65,14 @@ fun SwipeToDeleteContainer(
StartToEnd -> {
onStartToEnd()
}
EndToStart -> return@rememberSwipeToDismissBoxState false
Settled -> return@rememberSwipeToDismissBoxState false
EndToStart -> {
return@rememberSwipeToDismissBoxState false
}
Settled -> {
return@rememberSwipeToDismissBoxState false
}
}
return@rememberSwipeToDismissBoxState true
},
@@ -139,7 +139,7 @@ fun ZoomableContentView(
var dialogOpen by remember(content) { mutableStateOf(false) }
when (content) {
is MediaUrlImage ->
is MediaUrlImage -> {
SensitivityWarning(content.contentWarning != null, accountViewModel) {
TwoSecondController(content) { controllerVisible ->
val mainImageModifier =
@@ -150,7 +150,9 @@ fun ZoomableContentView(
UrlImageView(content, contentScale, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel)
}
}
is MediaUrlVideo ->
}
is MediaUrlVideo -> {
SensitivityWarning(content.contentWarning != null, accountViewModel) {
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
VideoView(
@@ -171,7 +173,9 @@ fun ZoomableContentView(
)
}
}
is MediaLocalImage ->
}
is MediaLocalImage -> {
TwoSecondController(content) { controllerVisible ->
val mainImageModifier =
Modifier
@@ -181,7 +185,9 @@ fun ZoomableContentView(
LocalImageView(content, contentScale, mainImageModifier, loadedImageModifier, controllerVisible, accountViewModel = accountViewModel)
}
is MediaLocalVideo ->
}
is MediaLocalVideo -> {
content.localFile?.let {
Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
VideoView(
@@ -198,6 +204,7 @@ fun ZoomableContentView(
)
}
}
}
}
if (dialogOpen) {
@@ -286,9 +293,11 @@ fun LocalImageView(
}
}
}
is AsyncImagePainter.State.Error -> {
BlankNote(loadedImageModifier)
}
is AsyncImagePainter.State.Success -> {
SubcomposeAsyncImageContent(loadedImageModifier)
@@ -310,6 +319,7 @@ fun LocalImageView(
}
}
}
else -> {}
}
}
@@ -405,9 +415,11 @@ fun UrlImageView(
}
}
}
is AsyncImagePainter.State.Error -> {
ClickableUrl(urlText = "${content.url} ", url = content.url)
}
is AsyncImagePainter.State.Success -> {
SubcomposeAsyncImageContent(loadedImageModifier)
@@ -418,6 +430,7 @@ fun UrlImageView(
MediaAspectRatioCache.add(content.url, drawable.width, drawable.height)
}
}
else -> {}
}
}
@@ -794,6 +807,7 @@ fun ShareMediaAction(
}
}
}
is MediaUrlVideo -> {
videoUri?.let {
if (videoUri.isNotEmpty()) {
@@ -832,6 +846,7 @@ fun ShareMediaAction(
}
}
}
is MediaLocalVideo -> {
content.localFile?.let { localFile ->
DropdownMenuItem(
@@ -843,6 +858,7 @@ fun ShareMediaAction(
)
}
}
else -> { /* No share option for other types */ }
}
}
@@ -39,7 +39,7 @@ fun DisplayErrorMessages(
openDialogMsg.value?.let { obj ->
when (obj) {
is ResourceToastMsg ->
is ResourceToastMsg -> {
if (obj.params != null) {
InformationDialog(
stringRes(obj.titleResId),
@@ -55,16 +55,18 @@ fun DisplayErrorMessages(
toastManager.clearToasts()
}
}
}
is StringToastMsg ->
is StringToastMsg -> {
InformationDialog(
obj.title,
obj.msg,
) {
toastManager.clearToasts()
}
}
is ActionableStringToastMsg ->
is ActionableStringToastMsg -> {
InformationDialog(
obj.title,
obj.msg,
@@ -72,8 +74,9 @@ fun DisplayErrorMessages(
obj.action()
toastManager.clearToasts()
}
}
is ThrowableToastMsg ->
is ThrowableToastMsg -> {
InformationDialog(
stringRes(obj.titleResId),
obj.msg,
@@ -81,8 +84,11 @@ fun DisplayErrorMessages(
) {
toastManager.clearToasts()
}
}
is MultiErrorToastMsg -> MultiUserErrorMessageDialog(obj, accountViewModel, nav)
is MultiErrorToastMsg -> {
MultiUserErrorMessageDialog(obj, accountViewModel, nav)
}
}
}
}
@@ -87,24 +87,29 @@ fun routeForInner(
is AppDefinitionEvent -> {
Route.ContentDiscovery(noteEvent.id)
}
is IsInPublicChatChannel -> {
noteEvent.channelId()?.let {
Route.PublicChatChannel(it)
}
}
is ChannelCreateEvent -> {
Route.PublicChatChannel(noteEvent.id)
}
is LiveActivitiesEvent -> {
noteEvent.address().let {
Route.LiveActivityChannel(it.kind, it.pubKeyHex, it.dTag)
}
}
is LiveActivitiesChatMessageEvent -> {
noteEvent.activityAddress()?.let {
Route.LiveActivityChannel(it.kind, it.pubKeyHex, it.dTag)
}
}
is EphemeralChatEvent -> {
noteEvent.roomId()?.let {
Route.EphemeralChat(it.id, it.relayUrl.url)
@@ -124,16 +129,19 @@ fun routeForInner(
is CommunityDefinitionEvent -> {
Route.Community(noteEvent.kind, noteEvent.pubKey, noteEvent.dTag())
}
is GiftWrapEvent -> {
noteEvent.innerEventId?.let {
routeFor(LocalCache.getOrCreateNote(it), loggedIn)
}
}
is SealedRumorEvent -> {
noteEvent.innerEventId?.let {
routeFor(LocalCache.getOrCreateNote(it), loggedIn)
}
}
is AddressableEvent -> {
Route.Note(noteEvent.aTag().toTag())
}
@@ -232,23 +240,31 @@ fun routeReplyTo(
Route.PublicChatChannel(channelId, replyTo = note.idHex)
}
}
is LiveActivitiesChatMessageEvent -> {
noteEvent.activityAddress()?.let {
Route.LiveActivityChannel(it.kind, it.pubKeyHex, it.dTag, replyTo = note.idHex)
}
}
is EphemeralChatEvent -> {
noteEvent.roomId()?.let {
Route.EphemeralChat(it.id, it.relayUrl.url, replyTo = note.idHex)
}
}
is PublicMessageEvent ->
is PublicMessageEvent -> {
Route.NewPublicMessage(
users = noteEvent.groupKeySet() - account.userProfile().pubkeyHex,
parentId = noteEvent.id,
)
is TextNoteEvent -> Route.NewShortNote(baseReplyTo = note.idHex)
is PrivateDmEvent ->
}
is TextNoteEvent -> {
Route.NewShortNote(baseReplyTo = note.idHex)
}
is PrivateDmEvent -> {
routeToMessage(
room = noteEvent.chatroomKey(account.userProfile().pubkeyHex),
draftMessage = null,
@@ -256,7 +272,9 @@ fun routeReplyTo(
draftId = null,
account = account,
)
is ChatroomKeyable ->
}
is ChatroomKeyable -> {
routeToMessage(
room = noteEvent.chatroomKey(account.userProfile().pubkeyHex),
draftMessage = null,
@@ -264,6 +282,8 @@ fun routeReplyTo(
draftId = null,
account = account,
)
}
is CommentEvent -> {
if (noteEvent.isGeohashedScoped()) {
Route.GeoPost(replyTo = note.idHex)
@@ -274,7 +294,9 @@ fun routeReplyTo(
}
}
else -> Route.GenericCommentPost(replyTo = note.idHex)
else -> {
Route.GenericCommentPost(replyTo = note.idHex)
}
}
}
@@ -286,10 +308,14 @@ suspend fun routeEditDraftTo(
val draft = account.draftsDecryptionCache.cachedDraft(noteEvent)
return when (draft) {
is ChannelMessageEvent -> draft.channelId()?.let { Route.PublicChatChannel(it, draftId = note.idHex) }
is ChannelMessageEvent -> {
draft.channelId()?.let { Route.PublicChatChannel(it, draftId = note.idHex) }
}
is LiveActivitiesChatMessageEvent -> {
draft.activityAddress()?.let { Route.LiveActivityChannel(it.kind, it.pubKeyHex, it.dTag, draftId = note.idHex) }
}
is EphemeralChatEvent -> {
draft.roomId()?.let { Route.EphemeralChat(it.id, it.relayUrl.url, draftId = note.idHex) }
}
@@ -300,15 +326,21 @@ suspend fun routeEditDraftTo(
return Route.Room(room, draftId = note.idHex)
}
is TextNoteEvent -> Route.NewShortNote(draft = note.idHex)
is ClassifiedsEvent -> Route.NewProduct(draft = note.idHex)
is TextNoteEvent -> {
Route.NewShortNote(draft = note.idHex)
}
is PublicMessageEvent ->
is ClassifiedsEvent -> {
Route.NewProduct(draft = note.idHex)
}
is PublicMessageEvent -> {
Route.NewPublicMessage(
users = draft.groupKeySet() - account.userProfile().pubkeyHex,
parentId = noteEvent.id,
draftId = note.idHex,
)
}
is CommentEvent -> {
if (draft.isGeohashedScoped()) {
@@ -320,6 +352,8 @@ suspend fun routeEditDraftTo(
}
}
else -> null
else -> {
null
}
}
}
@@ -312,7 +312,6 @@ fun getRouteWithArguments(navController: NavHostController): Route? {
dest.hasRoute<Route.Video>() -> entry.toRoute<Route.Video>()
dest.hasRoute<Route.Discover>() -> entry.toRoute<Route.Discover>()
dest.hasRoute<Route.Notification>() -> entry.toRoute<Route.Notification>()
dest.hasRoute<Route.Search>() -> entry.toRoute<Route.Search>()
dest.hasRoute<Route.SecurityFilters>() -> entry.toRoute<Route.SecurityFilters>()
dest.hasRoute<Route.PrivacyOptions>() -> entry.toRoute<Route.PrivacyOptions>()
@@ -321,16 +320,13 @@ fun getRouteWithArguments(navController: NavHostController): Route? {
dest.hasRoute<Route.Drafts>() -> entry.toRoute<Route.Drafts>()
dest.hasRoute<Route.Settings>() -> entry.toRoute<Route.Settings>()
dest.hasRoute<Route.EditProfile>() -> entry.toRoute<Route.EditProfile>()
dest.hasRoute<Route.Profile>() -> entry.toRoute<Route.Profile>()
dest.hasRoute<Route.Note>() -> entry.toRoute<Route.Note>()
dest.hasRoute<Route.Hashtag>() -> entry.toRoute<Route.Hashtag>()
dest.hasRoute<Route.Geohash>() -> entry.toRoute<Route.Geohash>()
dest.hasRoute<Route.Community>() -> entry.toRoute<Route.Community>()
dest.hasRoute<Route.QRDisplay>() -> entry.toRoute<Route.QRDisplay>()
dest.hasRoute<Route.RelayInfo>() -> entry.toRoute<Route.RelayInfo>()
dest.hasRoute<Route.RoomByAuthor>() -> entry.toRoute<Route.RoomByAuthor>()
dest.hasRoute<Route.PublicChatChannel>() -> entry.toRoute<Route.PublicChatChannel>()
dest.hasRoute<Route.LiveActivityChannel>() -> entry.toRoute<Route.LiveActivityChannel>()
@@ -349,7 +345,6 @@ fun getRouteWithArguments(navController: NavHostController): Route? {
dest.hasRoute<Route.HashtagPost>() -> entry.toRoute<Route.HashtagPost>()
dest.hasRoute<Route.GenericCommentPost>() -> entry.toRoute<Route.GenericCommentPost>()
dest.hasRoute<Route.NewPublicMessage>() -> entry.toRoute<Route.NewPublicMessage>()
dest.hasRoute<Route.Lists>() -> entry.toRoute<Route.Lists>()
dest.hasRoute<Route.MyPeopleListView>() -> entry.toRoute<Route.MyPeopleListView>()
dest.hasRoute<Route.MyFollowPackView>() -> entry.toRoute<Route.MyFollowPackView>()
@@ -359,10 +354,7 @@ fun getRouteWithArguments(navController: NavHostController): Route? {
dest.hasRoute<Route.NewGroupDM>() -> entry.toRoute<Route.NewGroupDM>()
dest.hasRoute<Route.UserSettings>() -> entry.toRoute<Route.UserSettings>()
dest.hasRoute<Route.ManualZapSplitPayment>() -> entry.toRoute<Route.ManualZapSplitPayment>()
else -> {
null
}
else -> null
}
}
@@ -179,6 +179,7 @@ fun FeedFilterSpinner(
lineHeight = 12.sp,
)
}
LocationState.LocationResult.Loading -> {
Text(
text = stringRes(R.string.loading_location),
@@ -252,6 +253,7 @@ fun RenderOption(
}
}
}
is HashtagName -> {
Row(
horizontalArrangement = Arrangement.Center,
@@ -260,6 +262,7 @@ fun RenderOption(
Text(text = option.name(), color = MaterialTheme.colorScheme.onSurface)
}
}
is ResourceName -> {
Row(
horizontalArrangement = Arrangement.Center,
@@ -271,6 +274,7 @@ fun RenderOption(
)
}
}
is PeopleListName -> {
Row(
horizontalArrangement = Arrangement.Center,
@@ -297,6 +301,7 @@ fun RenderOption(
Text(text = name, color = MaterialTheme.colorScheme.onSurface)
}
}
is CommunityName -> {
Row(
horizontalArrangement = Arrangement.Center,
@@ -221,8 +221,14 @@ fun RenderLikeGallery(
)
} else {
when (val shortReaction = reactionType) {
"+" -> LikedIcon(modifier.size(Size19dp))
"-" -> Text(text = "\uD83D\uDC4E", modifier = modifier)
"+" -> {
LikedIcon(modifier.size(Size19dp))
}
"-" -> {
Text(text = "\uD83D\uDC4E", modifier = modifier)
}
else -> {
if (EmojiCoder.isCoded(shortReaction)) {
DisplaySecretEmojiAsReaction(
@@ -243,13 +243,15 @@ fun DisplayStatusInner(
nav: INav,
) {
when (type) {
"music" ->
"music" -> {
Icon(
imageVector = CustomHashTagIcons.Tunestr,
null,
modifier = Size15Modifier.padding(end = Size5dp),
tint = MaterialTheme.colorScheme.placeholderText,
)
}
else -> {}
}
@@ -408,41 +410,50 @@ fun RenderNIP05VerifiedSymbol(
) {
CrossfadeIfEnabled(targetState = state, accountViewModel = accountViewModel) {
when (it) {
is Nip05VerifState.Verifying ->
is Nip05VerifState.Verifying -> {
Icon(
imageVector = Icons.Default.Downloading,
contentDescription = stringRes(id = R.string.nip05_checking),
modifier = modifier,
tint = Color.Yellow,
)
is Nip05VerifState.NotStarted ->
}
is Nip05VerifState.NotStarted -> {
Icon(
imageVector = Icons.Default.Downloading,
contentDescription = stringRes(id = R.string.nip05_checking),
modifier = modifier,
tint = Color.Yellow,
)
is Nip05VerifState.Verified ->
}
is Nip05VerifState.Verified -> {
Icon(
painter = painterRes(R.drawable.nip_05, compositionSizeReference),
contentDescription = stringRes(id = R.string.nip05_verified),
modifier = modifier,
tint = Color.Unspecified,
)
is Nip05VerifState.Failed ->
}
is Nip05VerifState.Failed -> {
Icon(
imageVector = Icons.Default.Report,
contentDescription = stringRes(id = R.string.nip05_failed),
modifier = modifier,
tint = Color.Red,
)
is Nip05VerifState.Error ->
}
is Nip05VerifState.Error -> {
Icon(
imageVector = Icons.Default.Report,
contentDescription = stringRes(id = R.string.nip05_failed),
modifier = modifier,
tint = Color.Red,
)
}
}
}
}
@@ -297,14 +297,16 @@ fun AcceptableNote(
if (isQuotedNote || isBoostedNote) {
val noteEvent = baseNote.event
when (noteEvent) {
is ChannelCreateEvent ->
is ChannelCreateEvent -> {
RenderPublicChatChannelHeader(
channelId = noteEvent.id,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
is ChannelMetadataEvent ->
}
is ChannelMetadataEvent -> {
noteEvent.channelId()?.let {
RenderPublicChatChannelHeader(
channelId = it,
@@ -313,7 +315,9 @@ fun AcceptableNote(
nav = nav,
)
}
is CommunityDefinitionEvent ->
}
is CommunityDefinitionEvent -> {
(baseNote as? AddressableNote)?.let {
RenderCommunity(
baseNote = it,
@@ -321,8 +325,13 @@ fun AcceptableNote(
nav = nav,
)
}
is BadgeDefinitionEvent -> BadgeDisplay(baseNote = baseNote, accountViewModel)
else ->
}
is BadgeDefinitionEvent -> {
BadgeDisplay(baseNote = baseNote, accountViewModel)
}
else -> {
LongPressToQuickAction(baseNote = baseNote, accountViewModel = accountViewModel, nav) { showPopup ->
CheckNewAndRenderNote(
baseNote = baseNote,
@@ -341,17 +350,20 @@ fun AcceptableNote(
moreOptions = moreOptions,
)
}
}
}
} else {
when (val noteEvent = baseNote.event) {
is ChannelCreateEvent ->
is ChannelCreateEvent -> {
RenderPublicChatChannelHeader(
channelId = noteEvent.id,
sendToChannel = true,
accountViewModel = accountViewModel,
nav = nav,
)
is ChannelMetadataEvent ->
}
is ChannelMetadataEvent -> {
noteEvent.channelId()?.let {
RenderPublicChatChannelHeader(
channelId = it,
@@ -360,7 +372,9 @@ fun AcceptableNote(
nav = nav,
)
}
is CommunityDefinitionEvent ->
}
is CommunityDefinitionEvent -> {
(baseNote as? AddressableNote)?.let {
RenderCommunity(
baseNote = it,
@@ -368,8 +382,13 @@ fun AcceptableNote(
nav = nav,
)
}
is BadgeDefinitionEvent -> BadgeDisplay(baseNote, accountViewModel)
else ->
}
is BadgeDefinitionEvent -> {
BadgeDisplay(baseNote, accountViewModel)
}
else -> {
LongPressToQuickAction(baseNote, accountViewModel, nav) { showPopup ->
CheckNewAndRenderNote(
baseNote = baseNote,
@@ -388,6 +407,7 @@ fun AcceptableNote(
moreOptions = moreOptions,
)
}
}
}
}
}
@@ -717,34 +737,118 @@ private fun RenderNoteRow(
nav: INav,
) {
when (val noteEvent = baseNote.event) {
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
is AudioTrackEvent -> RenderAudioTrack(baseNote, ContentScale.FillWidth, accountViewModel, nav)
is AudioHeaderEvent -> RenderAudioHeader(baseNote, ContentScale.FillWidth, accountViewModel, nav)
is DraftWrapEvent -> RenderDraft(baseNote, quotesLeft, unPackReply, backgroundColor, accountViewModel, nav)
is ReactionEvent -> RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
is RepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
is GenericRepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
is ReportEvent -> RenderReport(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
is LongTextNoteEvent -> RenderLongFormContent(baseNote, accountViewModel, nav)
is WikiNoteEvent -> RenderWikiContent(baseNote, accountViewModel, nav)
is NipTextEvent -> RenderNipContent(baseNote, accountViewModel, nav)
is BadgeAwardEvent -> RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
is FhirResourceEvent -> RenderFhirResource(baseNote, accountViewModel, nav)
is PeopleListEvent -> DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
is FollowListEvent -> DisplayFollowList(baseNote, true, accountViewModel, nav)
is RelaySetEvent -> DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav)
is ChatMessageRelayListEvent -> DisplayDMRelayList(baseNote, backgroundColor, accountViewModel, nav)
is AdvertisedRelayListEvent -> DisplayNIP65RelayList(baseNote, backgroundColor, accountViewModel, nav)
is SearchRelayListEvent -> DisplaySearchRelayList(baseNote, backgroundColor, accountViewModel, nav)
is BlockedRelayListEvent -> DisplayBlockedRelayList(baseNote, backgroundColor, accountViewModel, nav)
is TrustedRelayListEvent -> DisplayTrustedRelayList(baseNote, backgroundColor, accountViewModel, nav)
is IndexerRelayListEvent -> DisplayIndexerRelayList(baseNote, backgroundColor, accountViewModel, nav)
is ProxyRelayListEvent -> DisplayProxyRelayList(baseNote, backgroundColor, accountViewModel, nav)
is BroadcastRelayListEvent -> DisplayBroadcastRelayList(baseNote, backgroundColor, accountViewModel, nav)
is PinListEvent -> RenderPinListEvent(baseNote, backgroundColor, accountViewModel, nav)
is EmojiPackEvent -> RenderEmojiPack(baseNote, true, backgroundColor, accountViewModel)
is LiveActivitiesEvent -> RenderLiveActivityEvent(baseNote, accountViewModel, nav)
is GitRepositoryEvent -> RenderGitRepositoryEvent(baseNote, accountViewModel, nav)
is AppDefinitionEvent -> {
RenderAppDefinition(baseNote, accountViewModel, nav)
}
is AudioTrackEvent -> {
RenderAudioTrack(baseNote, ContentScale.FillWidth, accountViewModel, nav)
}
is AudioHeaderEvent -> {
RenderAudioHeader(baseNote, ContentScale.FillWidth, accountViewModel, nav)
}
is DraftWrapEvent -> {
RenderDraft(baseNote, quotesLeft, unPackReply, backgroundColor, accountViewModel, nav)
}
is ReactionEvent -> {
RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
}
is RepostEvent -> {
RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
}
is GenericRepostEvent -> {
RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
}
is ReportEvent -> {
RenderReport(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
}
is LongTextNoteEvent -> {
RenderLongFormContent(baseNote, accountViewModel, nav)
}
is WikiNoteEvent -> {
RenderWikiContent(baseNote, accountViewModel, nav)
}
is NipTextEvent -> {
RenderNipContent(baseNote, accountViewModel, nav)
}
is BadgeAwardEvent -> {
RenderBadgeAward(baseNote, backgroundColor, accountViewModel, nav)
}
is FhirResourceEvent -> {
RenderFhirResource(baseNote, accountViewModel, nav)
}
is PeopleListEvent -> {
DisplayPeopleList(baseNote, backgroundColor, accountViewModel, nav)
}
is FollowListEvent -> {
DisplayFollowList(baseNote, true, accountViewModel, nav)
}
is RelaySetEvent -> {
DisplayRelaySet(baseNote, backgroundColor, accountViewModel, nav)
}
is ChatMessageRelayListEvent -> {
DisplayDMRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is AdvertisedRelayListEvent -> {
DisplayNIP65RelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is SearchRelayListEvent -> {
DisplaySearchRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is BlockedRelayListEvent -> {
DisplayBlockedRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is TrustedRelayListEvent -> {
DisplayTrustedRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is IndexerRelayListEvent -> {
DisplayIndexerRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is ProxyRelayListEvent -> {
DisplayProxyRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is BroadcastRelayListEvent -> {
DisplayBroadcastRelayList(baseNote, backgroundColor, accountViewModel, nav)
}
is PinListEvent -> {
RenderPinListEvent(baseNote, backgroundColor, accountViewModel, nav)
}
is EmojiPackEvent -> {
RenderEmojiPack(baseNote, true, backgroundColor, accountViewModel)
}
is LiveActivitiesEvent -> {
RenderLiveActivityEvent(baseNote, accountViewModel, nav)
}
is GitRepositoryEvent -> {
RenderGitRepositoryEvent(baseNote, accountViewModel, nav)
}
is GitPatchEvent -> {
RenderGitPatchEvent(
baseNote,
@@ -756,6 +860,7 @@ private fun RenderNoteRow(
nav,
)
}
is GitIssueEvent -> {
RenderGitIssueEvent(
baseNote,
@@ -767,6 +872,7 @@ private fun RenderNoteRow(
nav,
)
}
is PrivateDmEvent -> {
RenderPrivateMessage(
baseNote,
@@ -778,6 +884,7 @@ private fun RenderNoteRow(
nav,
)
}
is ChatMessageEvent -> {
RenderChatMessage(
baseNote,
@@ -790,6 +897,7 @@ private fun RenderNoteRow(
nav,
)
}
is ChatMessageEncryptedFileHeaderEvent -> {
RenderChatMessageEncryptedFile(
baseNote,
@@ -802,6 +910,7 @@ private fun RenderNoteRow(
nav,
)
}
is ClassifiedsEvent -> {
RenderClassifieds(
noteEvent,
@@ -810,6 +919,7 @@ private fun RenderNoteRow(
nav,
)
}
is HighlightEvent -> {
RenderHighlight(
baseNote,
@@ -821,6 +931,7 @@ private fun RenderNoteRow(
nav,
)
}
is CommentEvent -> {
RenderTextEvent(
baseNote,
@@ -834,7 +945,8 @@ private fun RenderNoteRow(
nav,
)
}
is NIP90ContentDiscoveryResponseEvent ->
is NIP90ContentDiscoveryResponseEvent -> {
RenderNIP90ContentDiscoveryResponse(
baseNote,
makeItShort,
@@ -844,13 +956,15 @@ private fun RenderNoteRow(
accountViewModel,
nav,
)
}
is NIP90StatusEvent ->
is NIP90StatusEvent -> {
RenderNIP90Status(
baseNote,
accountViewModel,
nav,
)
}
is PollNoteEvent -> {
RenderPoll(
@@ -864,14 +978,39 @@ private fun RenderNoteRow(
nav,
)
}
is FileHeaderEvent -> FileHeaderDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel)
is VideoHorizontalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
is VideoVerticalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
is VideoNormalEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
is VideoShortEvent -> VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
is PictureEvent -> PictureDisplay(baseNote, true, ContentScale.FillWidth, PaddingValues(vertical = 5.dp), backgroundColor, accountViewModel, nav)
is BaseVoiceEvent -> RenderVoiceTrack(baseNote, accountViewModel, nav)
is FileStorageHeaderEvent -> FileStorageHeaderDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel)
is FileHeaderEvent -> {
FileHeaderDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel)
}
is VideoHorizontalEvent -> {
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
}
is VideoVerticalEvent -> {
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
}
is VideoNormalEvent -> {
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
}
is VideoShortEvent -> {
VideoDisplay(baseNote, makeItShort, canPreview, backgroundColor, ContentScale.FillWidth, accountViewModel, nav)
}
is PictureEvent -> {
PictureDisplay(baseNote, true, ContentScale.FillWidth, PaddingValues(vertical = 5.dp), backgroundColor, accountViewModel, nav)
}
is BaseVoiceEvent -> {
RenderVoiceTrack(baseNote, accountViewModel, nav)
}
is FileStorageHeaderEvent -> {
FileStorageHeaderDisplay(baseNote, true, ContentScale.FillWidth, accountViewModel)
}
is CommunityPostApprovalEvent -> {
RenderPostApproval(
baseNote,
@@ -881,6 +1020,7 @@ private fun RenderNoteRow(
nav,
)
}
is TextNoteModificationEvent -> {
RenderTextModificationEvent(
baseNote,
@@ -892,7 +1032,8 @@ private fun RenderNoteRow(
nav,
)
}
is ChannelMessageEvent ->
is ChannelMessageEvent -> {
RenderChannelMessage(
baseNote,
makeItShort,
@@ -903,7 +1044,9 @@ private fun RenderNoteRow(
accountViewModel,
nav,
)
is LiveActivitiesChatMessageEvent ->
}
is LiveActivitiesChatMessageEvent -> {
RenderLiveActivityChatMessage(
baseNote,
makeItShort,
@@ -914,15 +1057,18 @@ private fun RenderNoteRow(
accountViewModel,
nav,
)
is TorrentEvent ->
}
is TorrentEvent -> {
RenderTorrent(
baseNote,
backgroundColor,
accountViewModel,
nav,
)
}
is TorrentCommentEvent ->
is TorrentCommentEvent -> {
RenderTorrentComment(
baseNote,
makeItShort,
@@ -934,8 +1080,9 @@ private fun RenderNoteRow(
accountViewModel,
nav,
)
}
is InteractiveStoryBaseEvent ->
is InteractiveStoryBaseEvent -> {
RenderInteractiveStory(
baseNote,
makeItShort,
@@ -945,7 +1092,9 @@ private fun RenderNoteRow(
accountViewModel,
nav,
)
is PublicMessageEvent ->
}
is PublicMessageEvent -> {
RenderPublicMessage(
baseNote,
makeItShort,
@@ -955,6 +1104,8 @@ private fun RenderNoteRow(
accountViewModel,
nav,
)
}
else -> {
RenderTextEvent(
baseNote,
@@ -962,8 +962,14 @@ private fun RenderReactionType(
)
} else {
when (reactionType) {
"+" -> LikedIcon(iconSizeModifier)
"-" -> Text(text = "\uD83D\uDC4E", maxLines = 1, fontSize = iconFontSize)
"+" -> {
LikedIcon(iconSizeModifier)
}
"-" -> {
Text(text = "\uD83D\uDC4E", maxLines = 1, fontSize = iconFontSize)
}
else -> {
if (EmojiCoder.isCoded(reactionType)) {
AnimatedBorderTextCornerRadius(
@@ -1611,6 +1617,7 @@ fun RenderReaction(reactionType: String) {
fontSize = 22.sp,
)
}
else -> {
if (EmojiCoder.isCoded(reactionType)) {
AnimatedBorderTextCornerRadius(
@@ -326,12 +326,15 @@ private fun RenderReactionOption(
)
}
}
"-" ->
"-" -> {
Text(
text = "\uD83D\uDC4E ✖",
color = MaterialTheme.colorScheme.onBackground,
textAlign = TextAlign.Center,
)
}
else -> {
if (EmojiCoder.isCoded(reactionType)) {
Row(verticalAlignment = Alignment.CenterVertically) {
@@ -584,9 +584,15 @@ fun authenticate(
super.onAuthenticationError(errorCode, errString)
when (errorCode) {
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> keyguardPrompt()
BiometricPrompt.ERROR_LOCKOUT -> keyguardPrompt()
else ->
BiometricPrompt.ERROR_NEGATIVE_BUTTON -> {
keyguardPrompt()
}
BiometricPrompt.ERROR_LOCKOUT -> {
keyguardPrompt()
}
else -> {
onError(
stringRes(context, R.string.biometric_authentication_failed),
stringRes(
@@ -595,6 +601,7 @@ fun authenticate(
errString.toString(),
),
)
}
}
}
@@ -235,10 +235,13 @@ fun NonClickableUserPictures(
Box(Modifier.size(size), contentAlignment = Alignment.TopEnd) {
when (room.users.size) {
0 -> {}
1 ->
1 -> {
LoadUser(baseUserHex = room.users.first(), accountViewModel) {
it?.let { BaseUserPicture(it, size, accountViewModel, outerModifier = Modifier) }
}
}
2 -> {
val userList = room.users.toList()
@@ -263,6 +266,7 @@ fun NonClickableUserPictures(
}
}
}
3 -> {
val userList = room.users.toList()
@@ -297,6 +301,7 @@ fun NonClickableUserPictures(
}
}
}
else -> {
val userList = room.users.toList()
@@ -238,9 +238,11 @@ fun ZapCustomDialog(
LnZapEvent.ZapType.PUBLIC, LnZapEvent.ZapType.ANONYMOUS -> {
Text(text = stringRes(id = R.string.custom_zaps_add_a_message))
}
LnZapEvent.ZapType.PRIVATE -> {
Text(text = stringRes(id = R.string.custom_zaps_add_a_message_private))
}
LnZapEvent.ZapType.NONZAP -> {
Text(text = stringRes(id = R.string.custom_zaps_add_a_message_nonzap))
}
@@ -52,6 +52,7 @@ fun DisplayLocationObserver(viewModel: ILocationGrabber) {
lineHeight = 12.sp,
)
}
LocationState.LocationResult.Loading -> {
Text(
text = stringRes(R.string.loading_location),
@@ -235,6 +235,7 @@ private fun MyLoadUrlPreviewDirect(
}
}
}
else -> {
Box(contentAlignment = Alignment.BottomCenter, modifier = Modifier.aspectRatio(1f)) {
ClickableUrl(urlText, url)
@@ -288,11 +289,13 @@ private fun MyLoadUrlPreviewDirectFillWidth(
UrlPreviewCard(url, previewInfo = state.previewInfo)
}
}
is UrlPreviewState.Loading -> {
WaitAndDisplay {
DisplayUrlWithLoadingSymbol(url)
}
}
else -> {
ClickableUrl(urlText, url)
}
@@ -72,15 +72,18 @@ fun DisplayZapSplits(
FlowRow {
list.forEach {
when (it) {
is ZapSplitSetupLnAddress ->
is ZapSplitSetupLnAddress -> {
ClickableTextPrimary(it.lnAddress) { }
is ZapSplitSetup ->
}
is ZapSplitSetup -> {
UserPicture(
userHex = it.pubKeyHex,
size = Size25dp,
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
}
@@ -34,8 +34,14 @@ fun DisplayExternalId(
nav: INav,
) {
when (externalId) {
is GeohashId -> DisplayGeohashExternalId(externalId, accountViewModel, nav)
is HashtagId -> DisplayHashtagExternalId(externalId, accountViewModel, nav)
is GeohashId -> {
DisplayGeohashExternalId(externalId, accountViewModel, nav)
}
is HashtagId -> {
DisplayHashtagExternalId(externalId, accountViewModel, nav)
}
else -> {}
}
}
@@ -176,6 +176,7 @@ fun RenderLiveActivityEventInner(
}
StatusTag.STATUS.ENDED -> {}
null -> {}
}
}
@@ -201,9 +201,11 @@ fun RenderFhirResource(event: FhirResourceEvent) {
RenderEyeGlassesPrescription(it, state.localDb)
}
}
is VisionPrescription -> {
RenderEyeGlassesPrescription(resource, state.localDb)
}
else -> {
}
}
@@ -119,12 +119,7 @@ class AccountStateViewModel : ViewModel() {
is NEmbed -> null
is NRelay -> null
is NAddress -> null
else ->
try {
if (loginWithExternalSigner) Hex.decode(key) else null
} catch (e: Exception) {
null
}
else -> runCatching { if (loginWithExternalSigner) Hex.decode(key) else null }
}
if (loginWithExternalSigner && pubKeyParsed == null) {
@@ -350,11 +345,15 @@ class AccountStateViewModel : ViewModel() {
fun currentAccountNPub() =
when (val state = _accountContent.value) {
is AccountState.LoggedIn ->
is AccountState.LoggedIn -> {
state.account.signer.pubKey
.hexToByteArray()
.toNpub()
else -> null
}
else -> {
null
}
}
fun logOff(accountInfo: AccountInfo) {
@@ -64,12 +64,15 @@ fun UserFeedView(
is UserFeedState.Empty -> {
FeedEmpty { viewModel.invalidateData() }
}
is UserFeedState.FeedError -> {
FeedError(state.errorMessage) { viewModel.invalidateData() }
}
is UserFeedState.Loaded -> {
FeedLoaded(state, accountViewModel, nav)
}
is UserFeedState.Loading -> {
LoadingFeed()
}
@@ -1637,19 +1637,24 @@ class AccountViewModel(
when (val parsed = it.entity) {
is NSec -> {}
is NPub -> {}
is NProfile -> {}
is NNote -> {
LocalCache.checkGetOrCreateNote(parsed.hex)?.let { note ->
returningNote = note
}
}
is NEvent -> {
LocalCache.checkGetOrCreateNote(parsed.hex)?.let { note ->
returningNote = note
}
}
is NEmbed ->
is NEmbed -> {
withContext(Dispatchers.IO) {
val baseNote = LocalCache.getOrCreateNote(parsed.event)
if (baseNote.event == null) {
@@ -1660,13 +1665,16 @@ class AccountViewModel(
returningNote = baseNote
}
}
is NRelay -> {}
is NAddress -> {
LocalCache.checkGetOrCreateNote(parsed.aTag())?.let { note ->
returningNote = note
}
}
else -> {}
}
@@ -195,19 +195,24 @@ class DraftEventHandler(
draftEventWrap.replyTo?.forEach { it.addReply(draftEventWrap) }
when (rumor) {
is ChatroomKeyable -> account.chatroomList.add(rumor, draftEventWrap)
is ChatroomKeyable -> {
account.chatroomList.add(rumor, draftEventWrap)
}
is EphemeralChatEvent -> {
rumor.roomId()?.let { roomId ->
val channel = cache.getOrCreateEphemeralChannel(roomId)
channel.addNote(draftEventWrap, null)
}
}
is ChannelMessageEvent -> {
rumor.channelId()?.let { channelId ->
val channel = cache.checkGetOrCreatePublicChatChannel(channelId)
channel?.addNote(draftEventWrap, null)
}
}
is LiveActivitiesChatMessageEvent -> {
rumor.activityAddress()?.let { channelId ->
val channel = cache.getOrCreateLiveChannel(channelId)
@@ -118,21 +118,23 @@ private fun RenderBookmarkScreen(
Column(Modifier.padding(it).fillMaxHeight()) {
HorizontalPager(state = pagerState) { page ->
when (page) {
0 ->
0 -> {
RefresheableFeedView(
privateFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
}
1 ->
1 -> {
RefresheableFeedView(
publicFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
}
@@ -53,7 +53,7 @@ fun RenderArticleList(
HorizontalPager(pagerState, modifier) { page ->
when (page) {
0 ->
0 -> {
ArticleList(
modifier = Modifier.fillMaxSize(),
articles = publicArticles,
@@ -67,7 +67,9 @@ fun RenderArticleList(
accountViewModel = accountViewModel,
nav = nav,
)
1 ->
}
1 -> {
ArticleList(
modifier = Modifier.fillMaxSize(),
articles = privateArticles,
@@ -81,6 +83,7 @@ fun RenderArticleList(
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
}
@@ -157,7 +157,7 @@ fun BookmarkGroupScreenView(
.imePadding(),
) {
when (bookmarkType) {
BookmarkType.PostBookmark ->
BookmarkType.PostBookmark -> {
RenderPostList(
bookmarkGroupViewModel,
pagerState,
@@ -182,7 +182,9 @@ fun BookmarkGroupScreenView(
},
nav,
)
BookmarkType.ArticleBookmark ->
}
BookmarkType.ArticleBookmark -> {
RenderArticleList(
bookmarkGroupViewModel,
pagerState,
@@ -207,6 +209,7 @@ fun BookmarkGroupScreenView(
},
nav,
)
}
}
}
}
@@ -246,26 +249,32 @@ fun BookmarkGroupHeaderTabs(
val bookmarkGroup by bookmarkGroupViewModel.selectedBookmarkGroupFlow.collectAsStateWithLifecycle()
val privateItemTypeLabel =
when (bookmarkType) {
BookmarkType.PostBookmark ->
BookmarkType.PostBookmark -> {
bookmarkGroup?.let {
stringRes(R.string.private_posts_count, it.privatePostBookmarks.size)
} ?: stringRes(R.string.private_posts_label)
BookmarkType.ArticleBookmark ->
}
BookmarkType.ArticleBookmark -> {
bookmarkGroup?.let {
stringRes(R.string.private_articles_count, it.privateArticleBookmarks.size)
} ?: stringRes(R.string.private_posts_label)
}
}
val publicItemTypeLabel =
when (bookmarkType) {
BookmarkType.PostBookmark ->
BookmarkType.PostBookmark -> {
bookmarkGroup?.let {
stringRes(R.string.public_posts_count, it.publicPostBookmarks.size)
} ?: stringRes(R.string.public_posts_label)
BookmarkType.ArticleBookmark ->
}
BookmarkType.ArticleBookmark -> {
bookmarkGroup?.let {
stringRes(R.string.public_articles_count, it.publicArticleBookmarks.size)
} ?: stringRes(R.string.public_articles_label)
}
}
TabRow(
@@ -52,7 +52,7 @@ fun RenderPostList(
HorizontalPager(pagerState, modifier) { page ->
when (page) {
0 ->
0 -> {
PostList(
modifier = Modifier.fillMaxSize(),
posts = publicPosts,
@@ -66,7 +66,9 @@ fun RenderPostList(
accountViewModel = accountViewModel,
nav = nav,
)
1 ->
}
1 -> {
PostList(
modifier = Modifier.fillMaxSize(),
posts = privatePosts,
@@ -80,6 +82,7 @@ fun RenderPostList(
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
}
@@ -84,10 +84,19 @@ fun RenderChatFeedView(
CrossfadeIfEnabled(targetState = feedState, animationSpec = tween(durationMillis = 100), accountViewModel = accountViewModel) { state ->
when (state) {
is FeedState.Loading -> LoadingFeed()
is FeedState.Empty -> FeedEmpty { feed.invalidateData() }
is FeedState.FeedError -> FeedError(state.errorMessage) { feed.invalidateData() }
is FeedState.Loaded ->
is FeedState.Loading -> {
LoadingFeed()
}
is FeedState.Empty -> {
FeedEmpty { feed.invalidateData() }
}
is FeedState.FeedError -> {
FeedError(state.errorMessage) { feed.invalidateData() }
}
is FeedState.Loaded -> {
ChatFeedLoaded(
state,
accountViewModel,
@@ -98,6 +107,7 @@ fun RenderChatFeedView(
onWantsToEditDraft,
avoidDraft,
)
}
}
}
}
@@ -93,6 +93,7 @@ fun ChannelFileUploadDialog(
}
}
}
is LiveActivitiesChannel -> {
channel.creator?.let {
UserPicture(
@@ -120,26 +120,33 @@ private fun ChatroomEntry(
) {
val baseNoteEvent = lastMessage.event
when (baseNoteEvent) {
is ChannelMessageEvent ->
is ChannelMessageEvent -> {
baseNoteEvent.channelId()?.let {
LoadPublicChatChannel(it, accountViewModel) { channel ->
ChannelRoomCompose(lastMessage, channel, accountViewModel, nav)
}
}
is ChannelMetadataEvent ->
}
is ChannelMetadataEvent -> {
baseNoteEvent.channelId()?.let {
LoadPublicChatChannel(it, accountViewModel) { channel ->
ChannelRoomCompose(lastMessage, channel, accountViewModel, nav)
}
}
is ChannelCreateEvent ->
}
is ChannelCreateEvent -> {
LoadPublicChatChannel(baseNoteEvent.id, accountViewModel) { channel ->
ChannelRoomCompose(lastMessage, channel, accountViewModel, nav)
}
}
is ChatroomKeyable -> {
val room = baseNoteEvent.chatroomKey(accountViewModel.userProfile().pubkeyHex)
UserRoomCompose(room, lastMessage, accountViewModel, nav)
}
is EphemeralChatEvent -> {
baseNoteEvent.roomId()?.let {
LoadEphemeralChatChannel(it, accountViewModel) { channel ->
@@ -147,7 +154,10 @@ private fun ChatroomEntry(
}
}
}
else -> BlankNote()
else -> {
BlankNote()
}
}
}
@@ -47,9 +47,11 @@ fun MessagesScreen(
derivedStateOf {
when (windowSizeClass.widthSizeClass) {
WindowWidthSizeClass.Compact -> false
WindowWidthSizeClass.Expanded,
WindowWidthSizeClass.Medium,
-> true
else -> false
}
}
@@ -77,12 +77,15 @@ private fun CrossFadeState(
is FeedState.Empty -> {
FeedEmpty { feedContentState.invalidateData() }
}
is FeedState.FeedError -> {
FeedError(state.errorMessage) { feedContentState.invalidateData() }
}
is FeedState.Loaded -> {
FeedLoaded(state, listState, accountViewModel, nav)
}
FeedState.Loading -> {
LoadingFeed()
}
@@ -196,20 +196,23 @@ fun CommunityScreen(
state = pagerState,
) { page ->
when (page) {
0 ->
0 -> {
RefresheableFeedView(
feedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
1 ->
}
1 -> {
RefresheableFeedView(
modFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
}
@@ -305,9 +305,11 @@ private fun RenderDiscoverFeed(
is FeedState.Empty -> {
FeedEmpty(feedContentState::invalidateData)
}
is FeedState.FeedError -> {
FeedError(state.errorMessage, feedContentState::invalidateData)
}
is FeedState.Loaded -> {
DiscoverFeedColumnsLoaded(
state,
@@ -318,6 +320,7 @@ private fun RenderDiscoverFeed(
nav,
)
}
is FeedState.Loading -> {
LoadingFeed()
}
@@ -368,9 +371,11 @@ private fun RenderDiscoverFeed(
is FeedState.Empty -> {
FeedEmpty(feedContentState::invalidateData)
}
is FeedState.FeedError -> {
FeedError(state.errorMessage, feedContentState::invalidateData)
}
is FeedState.Loaded -> {
DiscoverFeedLoaded(
state,
@@ -381,6 +386,7 @@ private fun RenderDiscoverFeed(
nav,
)
}
is FeedState.Loading -> {
LoadingFeed()
}
@@ -124,9 +124,18 @@ open class DiscoverLiveFeedFilter(
2
}
}
StatusTag.STATUS.PLANNED -> 1
StatusTag.STATUS.ENDED -> 0
else -> 0
StatusTag.STATUS.PLANNED -> {
1
}
StatusTag.STATUS.ENDED -> {
0
}
else -> {
0
}
}
}
}
@@ -167,12 +167,15 @@ fun RenderLiveActivityThumb(
}
}
}
StatusTag.STATUS.ENDED -> {
EndedFlag()
}
StatusTag.STATUS.PLANNED -> {
ScheduledFlag(card.starts)
}
else -> {
EndedFlag()
}
@@ -176,26 +176,31 @@ fun FollowPackFeedScreen(
state = pagerState,
) { page ->
when (page) {
0 ->
0 -> {
RefresheableFeedView(
newThreadFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
1 ->
}
1 -> {
RefresheableFeedView(
conversationsFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav,
)
2 ->
}
2 -> {
UserFeedView(
membersFeedViewModel,
accountViewModel,
nav,
)
}
}
}
}
@@ -239,8 +239,12 @@ fun HomeScreenFloatingButton(
.collectAsStateWithLifecycle()
when (val myLocation = location) {
is LocationState.LocationResult.Success -> NewGeoPostButton(myLocation.geoHash.toString(), accountViewModel, nav)
is LocationState.LocationResult.Success -> {
NewGeoPostButton(myLocation.geoHash.toString(), accountViewModel, nav)
}
is LocationState.LocationResult.LackPermission -> { }
is LocationState.LocationResult.Loading -> { }
}
} else {
@@ -581,10 +581,22 @@ open class ShortNotePostViewModel :
val quotes = event.taggedQuotes()
if (quotes.isNotEmpty()) "Quote" else "Post"
}
is PollNoteEvent -> "Poll"
is VoiceEvent -> "Voice"
is VoiceReplyEvent -> "Voice Reply"
else -> "Post"
is PollNoteEvent -> {
"Poll"
}
is VoiceEvent -> {
"Voice"
}
is VoiceReplyEvent -> {
"Voice Reply"
}
else -> {
"Post"
}
}
suspend fun sendDraftSync() {
@@ -1115,6 +1127,7 @@ open class ShortNotePostViewModel :
voiceLocalFile = null
voiceRecording = null
}
is UploadOrchestrator.OrchestratorResult.NIP95Result -> {
// For NIP95, we need to create the event and get the nevent URL
// This is handled differently - skip for now
@@ -1122,6 +1135,7 @@ open class ShortNotePostViewModel :
}
}
}
is UploadingState.Error -> {
onError(uploadErrorTitle, uploadVoiceFailed)
voiceRecording = null
@@ -286,14 +286,17 @@ class VoiceReplyViewModel : ViewModel() {
onSuccess()
}
is UploadOrchestrator.OrchestratorResult.NIP95Result -> {
accountViewModel.toastManager.toast(uploadErrorTitle, uploadVoiceNip95NotSupported)
}
}
}
is UploadingState.Error -> {
accountViewModel.toastManager.toast(uploadErrorTitle, uploadVoiceFailed)
}
else -> {
accountViewModel.toastManager.toast(uploadErrorTitle, uploadVoiceFailed)
}
@@ -94,12 +94,16 @@ private suspend fun checkChannelIsOnline(
}
}
}
is EphemeralChatChannel -> {
// Check relay connection for ephemeral chat
val relayUrl = channel.roomId.relayUrl
OnlineChecker.isOnline(relayUrl.url, accountViewModel.httpClientBuilder::okHttpClientForVideo)
}
else -> false
else -> {
false
}
}
} catch (e: Exception) {
Log.d("LiveStatusIndicator", "Network error checking channel ${channel.toBestDisplayName()}: ${e.message}")
@@ -268,7 +268,7 @@ private fun PeopleListPager(
selectedSetState.value?.let { selectedSet ->
HorizontalPager(state = pagerState, modifier) { page ->
when (page) {
0 ->
0 -> {
PeopleListView(
memberList = selectedSet.publicMembersList,
onDeleteUser = { user ->
@@ -278,8 +278,9 @@ private fun PeopleListPager(
accountViewModel = accountViewModel,
nav = nav,
)
}
1 ->
1 -> {
PeopleListView(
memberList = selectedSet.privateMembersList,
onDeleteUser = { user ->
@@ -289,6 +290,7 @@ private fun PeopleListPager(
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
}
@@ -119,9 +119,11 @@ fun RenderCardFeed(
is CardFeedState.Empty -> {
NotificationFeedEmpty(feedContent::invalidateData)
}
is CardFeedState.FeedError -> {
FeedError(state.errorMessage, feedContent::invalidateData)
}
is CardFeedState.Loaded -> {
FeedLoaded(
loaded = state,
@@ -131,6 +133,7 @@ fun RenderCardFeed(
nav = nav,
)
}
CardFeedState.Loading -> {
LoadingFeed()
}
@@ -205,7 +208,7 @@ private fun RenderCardItem(
nav: INav,
) {
when (item) {
is NoteCard ->
is NoteCard -> {
NoteCardCompose(
item,
routeForLastRead = routeForLastRead,
@@ -214,7 +217,9 @@ private fun RenderCardItem(
accountViewModel = accountViewModel,
nav = nav,
)
is ZapUserSetCard ->
}
is ZapUserSetCard -> {
ZapUserSetCompose(
item,
isInnerNote = false,
@@ -222,7 +227,9 @@ private fun RenderCardItem(
nav = nav,
routeForLastRead = routeForLastRead,
)
is MultiSetCard ->
}
is MultiSetCard -> {
MultiSetCompose(
item,
accountViewModel = accountViewModel,
@@ -230,14 +237,18 @@ private fun RenderCardItem(
nav = nav,
routeForLastRead = routeForLastRead,
)
is BadgeCard ->
}
is BadgeCard -> {
BadgeCompose(
item,
accountViewModel = accountViewModel,
nav = nav,
routeForLastRead = routeForLastRead,
)
is MessageSetCard ->
}
is MessageSetCard -> {
MessageSetCompose(
messageSetCard = item,
routeForLastRead = routeForLastRead,
@@ -245,6 +256,7 @@ private fun RenderCardItem(
accountViewModel = accountViewModel,
nav = nav,
)
}
}
}
@@ -173,14 +173,17 @@ fun GalleryContentView(
) {
AutoNonlazyGrid(contentList.size) { contentIndex ->
when (val content = contentList[contentIndex]) {
is MediaUrlImage ->
is MediaUrlImage -> {
SensitivityWarning(content.contentWarning != null, accountViewModel) {
UrlImageView(content, accountViewModel, ratio = ratio)
}
is MediaUrlVideo ->
}
is MediaUrlVideo -> {
SensitivityWarning(content.contentWarning != null, accountViewModel) {
UrlVideoView(content, accountViewModel, ratio = ratio)
}
}
}
}
}
@@ -226,12 +229,15 @@ fun UrlImageView(
}
}
}
is AsyncImagePainter.State.Error -> {
ClickableUrl(urlText = "${content.url} ", url = content.url)
}
is AsyncImagePainter.State.Success -> {
SubcomposeAsyncImageContent(defaultModifier)
}
else -> {}
}
}
@@ -61,9 +61,11 @@ fun RenderGalleryFeed(
is FeedState.Empty -> {
FeedEmpty { viewModel.invalidateData() }
}
is FeedState.FeedError -> {
FeedError(state.errorMessage) { viewModel.invalidateData() }
}
is FeedState.Loaded -> {
GalleryFeedLoaded(
state,
@@ -72,6 +74,7 @@ fun RenderGalleryFeed(
nav,
)
}
is FeedState.Loading -> {
LoadingFeed()
}
@@ -51,8 +51,7 @@ class UserProfileGalleryFeedFilter(
acceptableEvent(it, params, user)
}
var sorted = sort(notes)
return sorted.toList()
return sort(notes).toList()
}
override fun applyFilter(newItems: Set<Note>): Set<Note> = innerApplyFilter(newItems)
@@ -70,7 +69,13 @@ class UserProfileGalleryFeedFilter(
): Boolean {
val noteEvent = it.event
return (
(it.event?.pubKey == user.pubkeyHex && (noteEvent is PictureEvent || noteEvent is VideoEvent || (noteEvent is ProfileGalleryEntryEvent) && noteEvent.hasUrl() && noteEvent.hasFromEvent())) // && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET))
(
it.event?.pubKey == user.pubkeyHex && (
noteEvent is PictureEvent ||
noteEvent is VideoEvent ||
(noteEvent is ProfileGalleryEntryEvent && noteEvent.hasUrl() && noteEvent.hasFromEvent())
)
) // && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET))
) &&
params.match(noteEvent, it.relays) &&
account.isAcceptable(it)
@@ -261,6 +261,7 @@ fun DisplayNip05ProfileStatus(
)
}
}
else -> { }
}
}
@@ -63,6 +63,7 @@ fun DisplayAppRecommendations(
Recommends(state, accountViewModel, nav)
}
}
else -> {}
}
}
@@ -263,13 +263,17 @@ fun WatchAndDisplayNip05Row(
val nip05StateMetadata by user.nip05State().flow.collectAsStateWithLifecycle()
when (val nip05State = nip05StateMetadata) {
is Nip05State.Exists -> ObserveAndDisplayNIP05(nip05State, accountViewModel)
else ->
is Nip05State.Exists -> {
ObserveAndDisplayNIP05(nip05State, accountViewModel)
}
else -> {
Text(
text = user.pubkeyDisplayHex(),
fontSize = Font14SP,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@@ -316,7 +316,7 @@ private fun RenderDebugMessage(msg: IRelayDebugMessage) {
}
when (msg) {
is ErrorDebugMessage ->
is ErrorDebugMessage -> {
SelectionContainer {
Text(
text = msg.message,
@@ -324,7 +324,9 @@ private fun RenderDebugMessage(msg: IRelayDebugMessage) {
color = MaterialTheme.colorScheme.onSurface,
)
}
is NoticeDebugMessage ->
}
is NoticeDebugMessage -> {
SelectionContainer {
Text(
text = msg.message,
@@ -332,7 +334,9 @@ private fun RenderDebugMessage(msg: IRelayDebugMessage) {
color = MaterialTheme.colorScheme.onSurface,
)
}
is SpamDebugMessage ->
}
is SpamDebugMessage -> {
SelectionContainer {
val uri = LocalUriHandler.current
val start = stringRes(R.string.duplicated_post)
@@ -358,6 +362,7 @@ private fun RenderDebugMessage(msg: IRelayDebugMessage) {
color = MaterialTheme.colorScheme.onSurface,
)
}
}
}
}
}
@@ -59,12 +59,15 @@ fun StringFeedView(
is StringFeedState.Empty -> {
StringFeedEmpty(pre, post) { viewModel.invalidateData() }
}
is StringFeedState.FeedError -> {
FeedError(state.errorMessage) { viewModel.invalidateData() }
}
is StringFeedState.Loaded -> {
FeedLoaded(state, pre, post, inner)
}
is StringFeedState.Loading -> {
LoadingFeed()
}
@@ -190,12 +190,15 @@ fun RenderPage(
is FeedState.Empty -> {
FeedEmpty(videoFeedContentState::invalidateData)
}
is FeedState.FeedError -> {
FeedError(state.errorMessage, videoFeedContentState::invalidateData)
}
is FeedState.Loaded -> {
LoadedState(state, pagerStateKey, videoFeedContentState, accountViewModel, nav)
}
is FeedState.Loading -> {
LoadingFeed()
}
@@ -155,18 +155,22 @@ fun LoginPage(
loginViewModel.errorManager.error?.let { error ->
when (error) {
is LoginErrorManager.SingleErrorMsg ->
is LoginErrorManager.SingleErrorMsg -> {
Text(
text = stringRes(error.errorResId),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
is LoginErrorManager.ParamsErrorMsg ->
}
is LoginErrorManager.ParamsErrorMsg -> {
Text(
text = stringRes(error.errorResId, *error.params),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
}
else -> {}
}
}
@@ -148,18 +148,22 @@ fun SignUpPage(
signUpViewModel.errorManager.error?.let { error ->
when (error) {
is LoginErrorManager.SingleErrorMsg ->
is LoginErrorManager.SingleErrorMsg -> {
Text(
text = stringRes(error.errorResId),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
is LoginErrorManager.ParamsErrorMsg ->
}
is LoginErrorManager.ParamsErrorMsg -> {
Text(
text = stringRes(error.errorResId, *error.params),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
)
}
else -> {}
}
}
@@ -540,12 +540,16 @@ fun AmethystTheme(
uiManager.nightMode = UiModeManager.MODE_NIGHT_YES
true
}
ThemeType.LIGHT -> {
val uiManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
uiManager.nightMode = UiModeManager.MODE_NIGHT_NO
false
}
else -> isSystemInDarkTheme()
else -> {
isSystemInDarkTheme()
}
}
val colors = if (darkTheme) DarkColorPalette else LightColorPalette
@@ -67,41 +67,57 @@ val DefaultParagraphSpacing: TextUnit = 18.sp
internal val DefaultHeadingStyle: HeadingStyle = { level, textStyle ->
when (level) {
0 ->
0 -> {
Typography.displayLarge.copy(
fontSize = 32.sp,
lineHeight = 40.sp,
)
1 ->
}
1 -> {
Typography.displayMedium.copy(
fontSize = 28.sp,
lineHeight = 36.sp,
)
2 ->
}
2 -> {
Typography.displaySmall.copy(
fontSize = 26.sp,
lineHeight = 34.sp,
)
3 ->
}
3 -> {
Typography.displaySmall.copy(
fontSize = 24.sp,
lineHeight = 32.sp,
)
4 ->
}
4 -> {
Typography.headlineLarge.copy(
fontSize = 22.sp,
lineHeight = 26.sp,
)
5 ->
}
5 -> {
Typography.headlineMedium.copy(
fontSize = 20.sp,
lineHeight = 24.sp,
)
6 ->
}
6 -> {
Typography.headlineSmall.copy(
fontSize = 18.sp,
lineHeight = 22.sp,
)
else -> textStyle
}
else -> {
textStyle
}
}
}
@@ -20,10 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.tor
import androidx.compose.runtime.Stable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
@Stable
class TorDialogViewModel : ViewModel() {
val torType = mutableStateOf(TorType.INTERNAL)
val socksPortStr = mutableStateOf("9050")
@@ -102,7 +104,7 @@ class TorDialogViewModel : ViewModel() {
TorPresetType.ONLY_WHEN_NEEDED -> resetOnlyFlags(torOnlyWhenNeededPreset)
TorPresetType.SMALL_PAYLOADS -> resetOnlyFlags(torSmallPayloadsPreset)
TorPresetType.FULL_PRIVACY -> resetOnlyFlags(torFullyPrivate)
TorPresetType.CUSTOM -> { }
TorPresetType.CUSTOM -> Unit
}
}
@@ -58,9 +58,11 @@ class TorManager(
TorType.INTERNAL -> {
emitAll(service.status)
}
TorType.OFF -> {
emit(TorServiceStatus.Off)
}
TorType.EXTERNAL -> {
if (externalSocksPort > 0) {
emit(TorServiceStatus.Active(externalSocksPort))
@@ -52,9 +52,7 @@ fun parseTorType(code: Int?): TorType =
TorType.OFF.screenCode -> TorType.OFF
TorType.INTERNAL.screenCode -> TorType.INTERNAL
TorType.EXTERNAL.screenCode -> TorType.EXTERNAL
else -> {
TorType.INTERNAL
}
else -> TorType.INTERNAL
}
enum class TorPresetType(
@@ -75,9 +73,7 @@ fun parseTorPresetType(code: Int?): TorPresetType =
TorPresetType.DEFAULT.screenCode -> TorPresetType.DEFAULT
TorPresetType.SMALL_PAYLOADS.screenCode -> TorPresetType.SMALL_PAYLOADS
TorPresetType.FULL_PRIVACY.screenCode -> TorPresetType.FULL_PRIVACY
else -> {
TorPresetType.CUSTOM
}
else -> TorPresetType.CUSTOM
}
fun isPreset(
@@ -257,7 +257,7 @@ fun PrivacySettingsBody(dialogViewModel: TorDialogViewModel) {
dialogViewModel.moneyOperationsViaTor,
)
/**
/*
* Too hard to separate Coil into regular images and profile pics
SwitchSettingsRow(
R.string.tor_use_profile_pictures,
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.relay.filters
import org.junit.Assert.assertTrue
import org.junit.Test
class FilterTest {
@Test
fun testIsEmpty() {
assertTrue(Filter().isEmpty())
assertTrue(Filter(ids = listOf()).isEmpty())
assertTrue(Filter(authors = listOf()).isEmpty())
assertTrue(Filter(kinds = listOf()).isEmpty())
assertTrue(Filter(tags = mapOf()).isEmpty())
assertTrue(Filter(tagsAll = mapOf()).isEmpty())
assertTrue(Filter(tags = mapOf("e" to listOf())).isEmpty())
assertTrue(Filter(tagsAll = mapOf("e" to listOf())).isEmpty())
assertTrue(Filter(search = "").isEmpty())
}
}
@@ -93,6 +93,7 @@ class EnsureTest {
(args[1].length == KEY_SIZE),
(args[1].isHex()),
-> null
else -> args[0] + args[1]
}
@@ -189,6 +189,7 @@ object BlurHashDecoderOld {
calculate -> {
DoubleArray(height * numCompY).also { cacheCosinesY[height * numCompY] = it }
}
else -> {
cacheCosinesY[height * numCompY]!!
}
@@ -202,7 +203,10 @@ object BlurHashDecoderOld {
calculate -> {
DoubleArray(width * numCompX).also { cacheCosinesX[width * numCompX] = it }
}
else -> cacheCosinesX[width * numCompX]!!
else -> {
cacheCosinesX[width * numCompX]!!
}
}
private fun DoubleArray.getCos(
@@ -60,6 +60,7 @@ object CosineCache {
cacheCosinesY.put(height * numCompY, it)
}
}
else -> {
cacheCosinesY[height * numCompY]!!
}
@@ -77,6 +78,9 @@ object CosineCache {
cos(PI * x * i / width)
}.also { cacheCosinesX.put(width * numCompX, it) }
}
else -> cacheCosinesX[width * numCompX]!!
else -> {
cacheCosinesX[width * numCompX]!!
}
}
}
@@ -191,7 +191,10 @@ open class Note(
fun relayHintUrl(): NormalizedRelayUrl? {
// checks Community Events first
when (val noteEvent = event) {
is CommunityDefinitionEvent -> noteEvent.relayUrls().firstOrNull()?.let { return it }
is CommunityDefinitionEvent -> {
noteEvent.relayUrls().firstOrNull()?.let { return it }
}
is IsInPublicChatChannel -> {
inGatherers?.forEach {
if (it is com.vitorpamplona.amethyst.commons.model.Channel) {
@@ -199,9 +202,11 @@ open class Note(
}
}
}
is LiveActivitiesEvent -> {
noteEvent.relays().ifEmpty { null }?.toSet()
}
is LiveActivitiesChatMessageEvent -> {
inGatherers?.forEach {
if (it is com.vitorpamplona.amethyst.commons.model.Channel) {
@@ -209,7 +214,10 @@ open class Note(
}
}
}
is EphemeralChatEvent -> noteEvent.roomId()?.let { return it.relayUrl }
is EphemeralChatEvent -> {
noteEvent.roomId()?.let { return it.relayUrl }
}
}
val currentOutbox = author?.outboxRelays()?.toSet()
@@ -108,14 +108,17 @@ object MetaTagsParser {
consume()
break
}
// `>` out of quote -> end of tag
quote == null && c == '>' -> {
break
}
// entering quote
quote == null && (c == '\'' || c == '"') -> {
quote = c
}
// leaving quote
quote != null && c == quote -> {
quote = null
@@ -263,6 +266,7 @@ object MetaTagsParser {
State.AFTER_EQ -> {
when {
c.isWhitespace() -> {}
c == '\'' || c == '"' -> {
valueBegin = i + 1
valueQuote = c
@@ -66,54 +66,63 @@ class OpenGraphParser {
metaTags.forEach {
when (it.attr(ATTRIBUTE_VALUE_PROPERTY)) {
in META_X_TITLE ->
in META_X_TITLE -> {
if (title.isEmpty()) {
title = it.attr(CONTENT)
}
}
in META_X_DESCRIPTION ->
in META_X_DESCRIPTION -> {
if (description.isEmpty()) {
description = it.attr(CONTENT)
}
}
in META_X_IMAGE ->
in META_X_IMAGE -> {
if (image.isEmpty()) {
image = it.attr(CONTENT)
}
}
}
when (it.attr(ATTRIBUTE_VALUE_NAME)) {
in META_X_TITLE ->
in META_X_TITLE -> {
if (title.isEmpty()) {
title = it.attr(CONTENT)
}
}
in META_X_DESCRIPTION ->
in META_X_DESCRIPTION -> {
if (description.isEmpty()) {
description = it.attr(CONTENT)
}
}
in META_X_IMAGE ->
in META_X_IMAGE -> {
if (image.isEmpty()) {
image = it.attr(CONTENT)
}
}
}
when (it.attr(ATTRIBUTE_VALUE_ITEMPROP)) {
in META_X_TITLE ->
in META_X_TITLE -> {
if (title.isEmpty()) {
title = it.attr(CONTENT)
}
}
in META_X_DESCRIPTION ->
in META_X_DESCRIPTION -> {
if (description.isEmpty()) {
description = it.attr(CONTENT)
}
}
in META_X_IMAGE ->
in META_X_IMAGE -> {
if (image.isEmpty()) {
image = it.attr(CONTENT)
}
}
}
if (title.isNotEmpty() && description.isNotEmpty() && image.isNotEmpty()) {
@@ -36,14 +36,24 @@ class GalleryParser {
paragraph.words.forEach { word ->
when (word) {
is ImageSegment, is Base64Segment -> imageCount++
is VideoSegment -> hasNonWhitespaceNonImageContent = true // Videos are not images
is ImageSegment, is Base64Segment -> {
imageCount++
}
is VideoSegment -> {
hasNonWhitespaceNonImageContent = true
}
// Videos are not images
is RegularTextSegment -> {
if (word.segmentText.isNotBlank()) {
hasNonWhitespaceNonImageContent = true
}
}
else -> hasNonWhitespaceNonImageContent = true // Links, emojis, etc.
else -> {
hasNonWhitespaceNonImageContent = true
} // Links, emojis, etc.
}
}
@@ -159,9 +169,16 @@ class GalleryParser {
hasVideo = true
break
}
seg is ImageSegment || seg is Base64Segment -> imageSegments.add(seg)
seg is ImageSegment || seg is Base64Segment -> {
imageSegments.add(seg)
}
seg is RegularTextSegment && seg.segmentText.isBlank() -> { /* skip whitespace */ }
else -> break
else -> {
break
}
}
j++
}
@@ -59,6 +59,7 @@ fun parseSearchInput(input: String): List<SearchResult> {
),
)
}
is NProfile -> {
results.add(
SearchResult.UserResult(
@@ -67,6 +68,7 @@ fun parseSearchInput(input: String): List<SearchResult> {
),
)
}
is NSec -> {
results.add(
SearchResult.UserResult(
@@ -75,6 +77,7 @@ fun parseSearchInput(input: String): List<SearchResult> {
),
)
}
is NNote -> {
results.add(
SearchResult.NoteResult(
@@ -83,6 +86,7 @@ fun parseSearchInput(input: String): List<SearchResult> {
),
)
}
is NEvent -> {
results.add(
SearchResult.NoteResult(
@@ -91,6 +95,7 @@ fun parseSearchInput(input: String): List<SearchResult> {
),
)
}
is NAddress -> {
results.add(
SearchResult.AddressResult(
@@ -101,6 +106,7 @@ fun parseSearchInput(input: String): List<SearchResult> {
),
)
}
else -> { }
}
return results
@@ -29,10 +29,16 @@ fun String.firstFullCharOld(): String {
return when (this.length) {
0,
1,
-> return this
-> {
return this
}
2,
3,
-> return if (isUTF16Char(0)) this.take(2) else this.take(1)
-> {
return if (isUTF16Char(0)) this.take(2) else this.take(1)
}
else -> {
val first = isUTF16Char(0)
val second = isUTF16Char(2)
@@ -62,19 +62,24 @@ fun timeAgo(
updateFormattersIfNeeded()
prefix + yearFormatter.format(time * 1000)
}
timeDifference > TimeUtils.ONE_MONTH -> {
updateFormattersIfNeeded()
prefix + monthFormatter.format(time * 1000)
}
timeDifference > TimeUtils.ONE_DAY -> {
prefix + (timeDifference / TimeUtils.ONE_DAY).toString() + "d"
}
timeDifference > TimeUtils.ONE_HOUR -> {
prefix + (timeDifference / TimeUtils.ONE_HOUR).toString() + "h"
}
timeDifference > TimeUtils.ONE_MINUTE -> {
prefix + (timeDifference / TimeUtils.ONE_MINUTE).toString() + "m"
}
else -> {
prefix + "now"
}
@@ -83,46 +88,22 @@ fun timeAgo(
fun timeDiffAgoLong(timeDifference: Int): String =
when {
timeDifference > TimeUtils.ONE_YEAR -> {
(timeDifference / TimeUtils.ONE_YEAR).toString() + " years"
}
timeDifference > TimeUtils.ONE_MONTH -> {
(timeDifference / TimeUtils.ONE_MONTH).toString() + " months"
}
timeDifference > TimeUtils.ONE_DAY -> {
(timeDifference / TimeUtils.ONE_DAY).toString() + " days"
}
timeDifference > TimeUtils.ONE_HOUR -> {
(timeDifference / TimeUtils.ONE_HOUR).toString() + " hours"
}
timeDifference > TimeUtils.ONE_MINUTE -> {
(timeDifference / TimeUtils.ONE_MINUTE).toString() + " minutes"
}
else -> {
"now"
}
timeDifference > TimeUtils.ONE_YEAR -> (timeDifference / TimeUtils.ONE_YEAR).toString() + " years"
timeDifference > TimeUtils.ONE_MONTH -> (timeDifference / TimeUtils.ONE_MONTH).toString() + " months"
timeDifference > TimeUtils.ONE_DAY -> (timeDifference / TimeUtils.ONE_DAY).toString() + " days"
timeDifference > TimeUtils.ONE_HOUR -> (timeDifference / TimeUtils.ONE_HOUR).toString() + " hours"
timeDifference > TimeUtils.ONE_MINUTE -> (timeDifference / TimeUtils.ONE_MINUTE).toString() + " minutes"
else -> "now"
}
fun timeDiffAgoShortish(timeDifference: Int): String =
when {
timeDifference > TimeUtils.ONE_YEAR -> {
(timeDifference / TimeUtils.ONE_YEAR).toString() + " yrs"
}
timeDifference > TimeUtils.ONE_MONTH -> {
(timeDifference / TimeUtils.ONE_MONTH).toString() + " mos"
}
timeDifference > TimeUtils.ONE_DAY -> {
(timeDifference / TimeUtils.ONE_DAY).toString() + " days"
}
timeDifference > TimeUtils.ONE_HOUR -> {
(timeDifference / TimeUtils.ONE_HOUR).toString() + " hrs"
}
timeDifference > TimeUtils.ONE_MINUTE -> {
(timeDifference / TimeUtils.ONE_MINUTE).toString() + " mins"
}
else -> {
"now"
}
timeDifference > TimeUtils.ONE_YEAR -> (timeDifference / TimeUtils.ONE_YEAR).toString() + " yrs"
timeDifference > TimeUtils.ONE_MONTH -> (timeDifference / TimeUtils.ONE_MONTH).toString() + " mos"
timeDifference > TimeUtils.ONE_DAY -> (timeDifference / TimeUtils.ONE_DAY).toString() + " days"
timeDifference > TimeUtils.ONE_HOUR -> (timeDifference / TimeUtils.ONE_HOUR).toString() + " hrs"
timeDifference > TimeUtils.ONE_MINUTE -> (timeDifference / TimeUtils.ONE_MINUTE).toString() + " mins"
else -> "now"
}
/**
@@ -148,11 +129,15 @@ fun dateFormatter(
updateFormattersIfNeeded()
yearFormatter.format(time * 1000)
}
timeDifference > TimeUtils.ONE_DAY -> {
updateFormattersIfNeeded()
monthFormatter.format(time * 1000)
}
else -> today
else -> {
today
}
}
}
@@ -296,6 +296,7 @@ fun App(
onLoginSuccess = { onScreenChange(DesktopScreen.Feed) },
)
}
is AccountState.LoggedIn -> {
val account = accountState as AccountState.LoggedIn
val nwcConnection by accountManager.nwcConnection.collectAsState()
@@ -442,7 +443,7 @@ fun MainContent(
modifier = Modifier.weight(1f).fillMaxHeight().padding(24.dp),
) {
when (currentScreen) {
DesktopScreen.Feed ->
DesktopScreen.Feed -> {
FeedScreen(
relayManager = relayManager,
localCache = localCache,
@@ -458,7 +459,9 @@ fun MainContent(
},
onZapFeedback = onZapFeedback,
)
DesktopScreen.Reads ->
}
DesktopScreen.Reads -> {
ReadsScreen(
relayManager = relayManager,
localCache = localCache,
@@ -470,7 +473,9 @@ fun MainContent(
onScreenChange(DesktopScreen.Thread(noteId))
},
)
DesktopScreen.Search ->
}
DesktopScreen.Search -> {
SearchScreen(
localCache = localCache,
relayManager = relayManager,
@@ -482,7 +487,9 @@ fun MainContent(
onScreenChange(DesktopScreen.Thread(noteId))
},
)
DesktopScreen.Bookmarks ->
}
DesktopScreen.Bookmarks -> {
BookmarksScreen(
relayManager = relayManager,
localCache = localCache,
@@ -497,9 +504,17 @@ fun MainContent(
},
onZapFeedback = onZapFeedback,
)
DesktopScreen.Messages -> MessagesPlaceholder()
DesktopScreen.Notifications -> NotificationsScreen(relayManager, account, subscriptionsCoordinator)
DesktopScreen.MyProfile ->
}
DesktopScreen.Messages -> {
MessagesPlaceholder()
}
DesktopScreen.Notifications -> {
NotificationsScreen(relayManager, account, subscriptionsCoordinator)
}
DesktopScreen.MyProfile -> {
UserProfileScreen(
pubKeyHex = account.pubKeyHex,
relayManager = relayManager,
@@ -514,7 +529,9 @@ fun MainContent(
},
onZapFeedback = onZapFeedback,
)
is DesktopScreen.UserProfile ->
}
is DesktopScreen.UserProfile -> {
UserProfileScreen(
pubKeyHex = currentScreen.pubKeyHex,
relayManager = relayManager,
@@ -529,7 +546,9 @@ fun MainContent(
},
onZapFeedback = onZapFeedback,
)
is DesktopScreen.Thread ->
}
is DesktopScreen.Thread -> {
ThreadScreen(
noteId = currentScreen.noteId,
relayManager = relayManager,
@@ -547,7 +566,11 @@ fun MainContent(
onZapFeedback = onZapFeedback,
onReply = onShowReplyDialog,
)
DesktopScreen.Settings -> RelaySettingsScreen(relayManager, account, accountManager)
}
DesktopScreen.Settings -> {
RelaySettingsScreen(relayManager, account, accountManager)
}
}
}
}
@@ -171,9 +171,11 @@ class NwcPaymentHandler(
is PayInvoiceSuccessResponse -> {
PaymentResult.Success(response.result?.preimage)
}
is PayInvoiceErrorResponse -> {
PaymentResult.Error(response.error?.message ?: "Unknown error")
}
else -> {
PaymentResult.Error("Unexpected response type: ${response.resultType}")
}

Some files were not shown because too many files have changed in this diff Show More