Merge branch 'vitorpamplona:main' into profiles-list-management

This commit is contained in:
KotlinGeekDev
2025-08-25 20:34:00 +00:00
committed by GitHub
13 changed files with 75 additions and 41 deletions
@@ -2632,7 +2632,7 @@ object LocalCache : ILocalCache {
// updates relay with a new event. // updates relay with a new event.
getAddressableNoteIfExists(event.address())?.let { note -> getAddressableNoteIfExists(event.address())?.let { note ->
note.event?.let { existingEvent -> note.event?.let { existingEvent ->
if (existingEvent.createdAt > event.createdAt && !note.hasRelay(relay.url) && !deletionIndex.hasBeenDeleted(event)) { if (existingEvent.createdAt > event.createdAt && !note.hasRelay(relay.url) && !deletionIndex.hasBeenDeleted(event) && !event.isExpired()) {
if (isDebug) { if (isDebug) {
Log.d("LocalCache", "Updating ${relay.url.url} with a new version of ${event.kind} ${event.id} to ${existingEvent.id}") Log.d("LocalCache", "Updating ${relay.url.url} with a new version of ${event.kind} ${event.id} to ${existingEvent.id}")
} }
@@ -57,10 +57,11 @@ class SearchRelayListState(
fun getSearchRelayList(): SearchRelayListEvent? = getSearchRelayListNote().event as? SearchRelayListEvent fun getSearchRelayList(): SearchRelayListEvent? = getSearchRelayListNote().event as? SearchRelayListEvent
suspend fun normalizeSearchRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> { fun searchListEvent(note: Note) = note.event as? SearchRelayListEvent ?: settings.backupSearchRelayList
val event = note.event as? SearchRelayListEvent ?: settings.backupSearchRelayList
return event?.let { decryptionCache.relays(it) } ?: DefaultSearchRelayList suspend fun normalizeSearchRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> = searchListEvent(note)?.let { decryptionCache.relays(it) }?.ifEmpty { null } ?: DefaultSearchRelayList
}
suspend fun normalizeSearchRelayListWithBackupNoDefaults(note: Note): Set<NormalizedRelayUrl> = searchListEvent(note)?.let { decryptionCache.relays(it) } ?: emptySet()
val flow = val flow =
getSearchRelayListFlow() getSearchRelayListFlow()
@@ -73,6 +74,17 @@ class SearchRelayListState(
emptySet(), emptySet(),
) )
val flowNoDefaults =
getSearchRelayListFlow()
.map { normalizeSearchRelayListWithBackupNoDefaults(it.note) }
.onStart { emit(normalizeSearchRelayListWithBackupNoDefaults(getSearchRelayListNote())) }
.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Companion.Eagerly,
emptySet(),
)
suspend fun saveRelayList(searchRelays: List<NormalizedRelayUrl>): SearchRelayListEvent { suspend fun saveRelayList(searchRelays: List<NormalizedRelayUrl>): SearchRelayListEvent {
val relayListForSearch = getSearchRelayList() val relayListForSearch = getSearchRelayList()
@@ -57,20 +57,15 @@ class Nip65RelayListState(
fun getNIP65RelayList(): AdvertisedRelayListEvent? = getNIP65RelayListNote().event as? AdvertisedRelayListEvent fun getNIP65RelayList(): AdvertisedRelayListEvent? = getNIP65RelayListNote().event as? AdvertisedRelayListEvent
fun normalizeNIP65WriteRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> { fun nip65Event(note: Note) = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList
val event = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList
return event?.writeRelaysNorm()?.toSet() ?: Constants.eventFinderRelays
}
fun normalizeNIP65ReadRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> { fun normalizeNIP65WriteRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> = nip65Event(note)?.writeRelaysNorm()?.toSet() ?: Constants.eventFinderRelays
val event = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList
return event?.readRelaysNorm()?.toSet() ?: Constants.eventFinderRelays
}
fun normalizeNIP65AllRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> { fun normalizeNIP65ReadRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> = nip65Event(note)?.readRelaysNorm()?.toSet() ?: Constants.eventFinderRelays
val event = note.event as? AdvertisedRelayListEvent ?: settings.backupNIP65RelayList
return event?.relays()?.map { it.relayUrl }?.toSet() ?: Constants.eventFinderRelays fun normalizeNIP65AllRelayListWithBackup(note: Note): Set<NormalizedRelayUrl> = nip65Event(note)?.relays()?.map { it.relayUrl }?.toSet() ?: Constants.eventFinderRelays
}
fun normalizeNIP65AllRelayListWithBackupNoDefaults(note: Note): Set<NormalizedRelayUrl> = nip65Event(note)?.relays()?.map { it.relayUrl }?.toSet() ?: emptySet()
val outboxFlow = val outboxFlow =
getNIP65RelayListFlow() getNIP65RelayListFlow()
@@ -105,6 +100,17 @@ class Nip65RelayListState(
emptySet(), emptySet(),
) )
val allFlowNoDefaults =
getNIP65RelayListFlow()
.map { normalizeNIP65AllRelayListWithBackupNoDefaults(it.note) }
.onStart { emit(normalizeNIP65AllRelayListWithBackupNoDefaults(getNIP65RelayListNote())) }
.flowOn(Dispatchers.Default)
.stateIn(
scope,
SharingStarted.Eagerly,
emptySet(),
)
suspend fun saveRelayList(relays: List<AdvertisedRelayInfo>): AdvertisedRelayListEvent { suspend fun saveRelayList(relays: List<AdvertisedRelayInfo>): AdvertisedRelayListEvent {
val nip65RelayList = getNIP65RelayList() val nip65RelayList = getNIP65RelayList()
@@ -52,11 +52,11 @@ class TrustedRelayListsState(
val flow: StateFlow<Set<NormalizedRelayUrl>> = val flow: StateFlow<Set<NormalizedRelayUrl>> =
combine( combine(
listOf( listOf(
nip65RelayList.allFlow, nip65RelayList.allFlowNoDefaults,
privateOutboxRelayList.flow, privateOutboxRelayList.flow,
localRelayList.flow, localRelayList.flow,
dmRelayList.flow, dmRelayList.flow,
searchRelayListState.flow, searchRelayListState.flowNoDefaults,
trustedRelayList.flow, trustedRelayList.flow,
broadcastRelayList.flow, broadcastRelayList.flow,
), ),
@@ -167,7 +167,7 @@ class EventNotificationConsumer(
// this is not verifiable // this is not verifiable
if (LocalCache.justConsume(inner, null, true)) { if (LocalCache.justConsume(inner, null, true)) {
event inner
} else { } else {
null null
} }
@@ -20,9 +20,11 @@
*/ */
package com.vitorpamplona.amethyst.ui.screen.loggedIn package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Build
import android.util.Log import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -30,11 +32,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
@@ -43,6 +47,7 @@ import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.RelayAuthSubscription import com.vitorpamplona.amethyst.service.relayClient.authCommand.compose.RelayAuthSubscription
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssemblerSubscription import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.navigation.AppNavigation import com.vitorpamplona.amethyst.ui.navigation.AppNavigation
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.scope
import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
@@ -51,7 +56,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.datasource.Discove
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssemblerSubscription
import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.VideoFilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.VideoFilterAssemblerSubscription
import com.vitorpamplona.quartz.nip55AndroidSigner.client.IActivityLauncher import com.vitorpamplona.quartz.nip55AndroidSigner.client.IActivityLauncher
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
@@ -140,15 +144,31 @@ fun ManageRelayServices(accountViewModel: AccountViewModel) {
.collectAsStateWithLifecycle() .collectAsStateWithLifecycle()
} }
@OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
fun NotificationRegistration(accountViewModel: AccountViewModel) { fun NotificationRegistration(accountViewModel: AccountViewModel) {
val scope = rememberCoroutineScope() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
var job = remember<Job?> { null } val scope = rememberCoroutineScope()
LifecycleResumeEffect(key1 = accountViewModel) { val notificationPermissionState = rememberPermissionState(Manifest.permission.POST_NOTIFICATIONS)
Log.d("RegisterAccounts", "Registering for push notifications")
job?.cancel() if (notificationPermissionState.status.isGranted) {
job = LifecycleResumeEffect(key1 = accountViewModel, notificationPermissionState.status.isGranted) {
Log.d("RegisterAccounts", "Registering for push notifications $notificationPermissionState")
scope.launch {
PushNotificationUtils.checkAndInit(
LocalPreferences.allSavedAccounts(),
accountViewModel::okHttpClientForTrustedRelays,
)
}
onPauseOrDispose {}
}
}
} else {
// no need for push permissions before 33
LifecycleResumeEffect(key1 = accountViewModel) {
Log.d("RegisterAccounts", "Registering for push notifications")
scope.launch { scope.launch {
PushNotificationUtils.checkAndInit( PushNotificationUtils.checkAndInit(
LocalPreferences.allSavedAccounts(), LocalPreferences.allSavedAccounts(),
@@ -156,8 +176,7 @@ fun NotificationRegistration(accountViewModel: AccountViewModel) {
) )
} }
onPauseOrDispose { onPauseOrDispose {}
job.cancel()
} }
} }
} }
@@ -68,11 +68,8 @@ class ChatMessageRelayListEvent(
val tags = val tags =
earlierVersion.tags earlierVersion.tags
.filter(RelayTag::notMatch) .filter(RelayTag::notMatch)
.plus( .plus(relays.map { RelayTag.assemble(it) })
relays.map { .toTypedArray()
RelayTag.assemble(it)
},
).toTypedArray()
return signer.sign(createdAt, KIND, tags, earlierVersion.content) return signer.sign(createdAt, KIND, tags, earlierVersion.content)
} }
@@ -34,7 +34,7 @@ class RelayTag {
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic @JvmStatic
fun notMatch(tag: Array<String>) = tag.has(0) && tag[0] == TAG_NAME fun notMatch(tag: Array<String>) = !(tag.has(0) && tag[0] == TAG_NAME)
@JvmStatic @JvmStatic
fun parse(tag: Array<String>): NormalizedRelayUrl? { fun parse(tag: Array<String>): NormalizedRelayUrl? {
@@ -33,7 +33,7 @@ class RelayTag {
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic @JvmStatic
fun notMatch(tag: Array<String>) = tag.has(0) && tag[0] == TAG_NAME fun notMatch(tag: Array<String>) = !(tag.has(0) && tag[0] == TAG_NAME)
@JvmStatic @JvmStatic
fun parse(tag: Array<String>): NormalizedRelayUrl? { fun parse(tag: Array<String>): NormalizedRelayUrl? {
@@ -33,7 +33,7 @@ class RelayTag {
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic @JvmStatic
fun notMatch(tag: Array<String>) = tag.has(0) && tag[0] == TAG_NAME fun notMatch(tag: Array<String>) = !(tag.has(0) && tag[0] == TAG_NAME)
@JvmStatic @JvmStatic
fun parse(tag: Array<String>): NormalizedRelayUrl? { fun parse(tag: Array<String>): NormalizedRelayUrl? {
@@ -33,7 +33,7 @@ class RelayTag {
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic @JvmStatic
fun notMatch(tag: Array<String>) = tag.has(0) && tag[0] == TAG_NAME fun notMatch(tag: Array<String>) = !(tag.has(0) && tag[0] == TAG_NAME)
@JvmStatic @JvmStatic
fun parse(tag: Array<String>): NormalizedRelayUrl? { fun parse(tag: Array<String>): NormalizedRelayUrl? {
@@ -33,7 +33,7 @@ class RelayTag {
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
@JvmStatic @JvmStatic
fun notMatch(tag: Array<String>) = tag.has(0) && tag[0] == TAG_NAME fun notMatch(tag: Array<String>) = !(tag.has(0) && tag[0] == TAG_NAME)
@JvmStatic @JvmStatic
fun shouldVanishFrom( fun shouldVanishFrom(
@@ -38,7 +38,7 @@ class AdvertisedRelayInfo(
fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() fun match(tag: Array<String>) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty()
fun notMatch(tag: Array<String>) = !match(tag) fun notMatch(tag: Array<String>) = !(tag.has(0) && tag[0] == TAG_NAME)
@JvmStatic @JvmStatic
fun parse(tag: Array<String>): AdvertisedRelayInfo? { fun parse(tag: Array<String>): AdvertisedRelayInfo? {