Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst: (35 commits) New Crowdin translations by GitHub Action fix chess tests Replace subsequent checks with '!isNullOrEmpty()' call use forEach: no intermediate list created. Use filterIsInstance to avoid dual iteration Lambda argument should be moved out of parentheses replace null checks with Elvis merge call chain with flatMap (slight performance improvement) New Crowdin translations by GitHub Action New Crowdin translations by GitHub Action update cz, pt, de, sv was this meant to be withContext? add names to boolean params remove unused imports Explicit type arguments can be inferred remove redundant modifiers correct package name If-Null return/break/... foldable to '?:' Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable. Declaration has type inferred from a platform call, which can lead to unchecked nullability issues. Specify type explicitly as nullable or non-nullable. ... # Conflicts: # amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ReactionsSettingsScreen.kt
This commit is contained in:
@@ -80,7 +80,7 @@ class ImageUploadTesting {
|
|||||||
.addInterceptor(DefaultContentTypeInterceptor("Amethyst/${BuildConfig.VERSION_NAME}"))
|
.addInterceptor(DefaultContentTypeInterceptor("Amethyst/${BuildConfig.VERSION_NAME}"))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private suspend fun getBitmap(): ByteArray {
|
private fun getBitmap(): ByteArray {
|
||||||
val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
|
val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
|
||||||
for (x in 0 until bitmap.width) {
|
for (x in 0 until bitmap.width) {
|
||||||
for (y in 0 until bitmap.height) {
|
for (y in 0 until bitmap.height) {
|
||||||
@@ -127,7 +127,7 @@ class ImageUploadTesting {
|
|||||||
// assertEquals(server.baseUrl, "${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png"))
|
// assertEquals(server.baseUrl, "${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png"))
|
||||||
|
|
||||||
val imageData: ByteArray =
|
val imageData: ByteArray =
|
||||||
ImageDownloader().waitAndGetImage(result.url!!, { client })?.bytes
|
ImageDownloader().waitAndGetImage(result.url!!) { client }?.bytes
|
||||||
?: run {
|
?: run {
|
||||||
fail("${server.name}: Should not be null")
|
fail("${server.name}: Should not be null")
|
||||||
return
|
return
|
||||||
@@ -142,8 +142,7 @@ class ImageUploadTesting {
|
|||||||
ServerInfoRetriever()
|
ServerInfoRetriever()
|
||||||
.loadInfo(
|
.loadInfo(
|
||||||
server.baseUrl,
|
server.baseUrl,
|
||||||
{ client },
|
) { client }
|
||||||
)
|
|
||||||
|
|
||||||
val payload = getBitmap()
|
val payload = getBitmap()
|
||||||
val inputStream = payload.inputStream()
|
val inputStream = payload.inputStream()
|
||||||
@@ -172,7 +171,7 @@ class ImageUploadTesting {
|
|||||||
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
|
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
|
||||||
|
|
||||||
val imageData: ByteArray =
|
val imageData: ByteArray =
|
||||||
ImageDownloader().waitAndGetImage(url, { client })?.bytes
|
ImageDownloader().waitAndGetImage(url) { client }?.bytes
|
||||||
?: run {
|
?: run {
|
||||||
fail("${server.name}: Should not be null")
|
fail("${server.name}: Should not be null")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -147,12 +147,14 @@ class AppModules(
|
|||||||
// Custom fetcher that considers tor settings and avoids forwarding.
|
// Custom fetcher that considers tor settings and avoids forwarding.
|
||||||
val nip05Fetcher = OkHttpNip05Fetcher(roleBasedHttpClientBuilder::okHttpClientForNip05)
|
val nip05Fetcher = OkHttpNip05Fetcher(roleBasedHttpClientBuilder::okHttpClientForNip05)
|
||||||
|
|
||||||
|
val namecoinElectrumxClient =
|
||||||
|
ElectrumXClient(
|
||||||
|
socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() },
|
||||||
|
)
|
||||||
|
|
||||||
val namecoinResolver =
|
val namecoinResolver =
|
||||||
NamecoinNameResolver(
|
NamecoinNameResolver(
|
||||||
electrumxClient =
|
electrumxClient = namecoinElectrumxClient,
|
||||||
ElectrumXClient(
|
|
||||||
socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() },
|
|
||||||
),
|
|
||||||
serverListProvider = {
|
serverListProvider = {
|
||||||
if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) {
|
if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) {
|
||||||
TOR_ELECTRUMX_SERVERS
|
TOR_ELECTRUMX_SERVERS
|
||||||
@@ -226,7 +228,7 @@ class AppModules(
|
|||||||
val relayStats = RelayStats(client)
|
val relayStats = RelayStats(client)
|
||||||
|
|
||||||
// Logs debug messages when needed
|
// Logs debug messages when needed
|
||||||
val detailedLogger = if (isDebug) RelayLogger(client, false, false) else null
|
val detailedLogger = if (isDebug) RelayLogger(client, debugSending = false, debugReceiving = false) else null
|
||||||
val relayReqStats = if (isDebug) RelayReqStats(client) else null
|
val relayReqStats = if (isDebug) RelayReqStats(client) else null
|
||||||
val logger = if (isDebug) RelaySpeedLogger(client) else null
|
val logger = if (isDebug) RelaySpeedLogger(client) else null
|
||||||
|
|
||||||
|
|||||||
@@ -1240,7 +1240,7 @@ class Account(
|
|||||||
val event = signer.sign(template)
|
val event = signer.sign(template)
|
||||||
cache.justConsumeMyOwnEvent(event)
|
cache.justConsumeMyOwnEvent(event)
|
||||||
val relays = relayList(event)
|
val relays = relayList(event)
|
||||||
if (relays != null && relays.isNotEmpty()) {
|
if (!relays.isNullOrEmpty()) {
|
||||||
client.send(event, relays.toSet())
|
client.send(event, relays.toSet())
|
||||||
} else {
|
} else {
|
||||||
client.send(event, computeRelayListToBroadcast(event))
|
client.send(event, computeRelayListToBroadcast(event))
|
||||||
@@ -1898,7 +1898,7 @@ class Account(
|
|||||||
fun getRelevantReports(note: Note): Set<Note> {
|
fun getRelevantReports(note: Note): Set<Note> {
|
||||||
val innerReports =
|
val innerReports =
|
||||||
if (note.event is RepostEvent || note.event is GenericRepostEvent) {
|
if (note.event is RepostEvent || note.event is GenericRepostEvent) {
|
||||||
note.replyTo?.map { getRelevantReports(it) }?.flatten() ?: emptyList()
|
note.replyTo?.flatMap { getRelevantReports(it) } ?: emptyList()
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,8 +186,8 @@ class AccountSettings(
|
|||||||
var backupEphemeralChatList: EphemeralChatListEvent? = null,
|
var backupEphemeralChatList: EphemeralChatListEvent? = null,
|
||||||
var backupTrustProviderList: TrustProviderListEvent? = null,
|
var backupTrustProviderList: TrustProviderListEvent? = null,
|
||||||
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
||||||
val hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf<String>()),
|
val hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
||||||
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow<Map<HexKey, String>>(mapOf()),
|
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow(mapOf()),
|
||||||
var backupNipA3PaymentTargets: PaymentTargetsEvent? = null,
|
var backupNipA3PaymentTargets: PaymentTargetsEvent? = null,
|
||||||
) : EphemeralChatRepository,
|
) : EphemeralChatRepository,
|
||||||
PublicChatListRepository {
|
PublicChatListRepository {
|
||||||
@@ -612,7 +612,7 @@ class AccountSettings(
|
|||||||
route: String,
|
route: String,
|
||||||
timestampInSecs: Long,
|
timestampInSecs: Long,
|
||||||
): MutableStateFlow<Long> =
|
): MutableStateFlow<Long> =
|
||||||
MutableStateFlow<Long>(timestampInSecs).also { newFlow ->
|
MutableStateFlow(timestampInSecs).also { newFlow ->
|
||||||
lastReadPerRoute.update { it + Pair(route, newFlow) }
|
lastReadPerRoute.update { it + Pair(route, newFlow) }
|
||||||
saveAccountSettings()
|
saveAccountSettings()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class AntiSpamFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val flowSpam = MutableStateFlow<AntiSpamState>(AntiSpamState(this))
|
val flowSpam = MutableStateFlow(AntiSpamState(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
class AntiSpamState(
|
class AntiSpamState(
|
||||||
|
|||||||
@@ -25,14 +25,13 @@ import java.lang.ref.WeakReference
|
|||||||
import java.util.concurrent.ConcurrentSkipListMap
|
import java.util.concurrent.ConcurrentSkipListMap
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
|
|
||||||
class LargeSoftCache<K, V> : CacheOperations<K, V> {
|
class LargeSoftCache<K : Any, V : Any> : CacheOperations<K, V> {
|
||||||
protected val cache = ConcurrentSkipListMap<K, WeakReference<V>>()
|
private val cache = ConcurrentSkipListMap<K, WeakReference<V>>()
|
||||||
|
|
||||||
fun keys() = cache.keys
|
fun keys() = cache.keys
|
||||||
|
|
||||||
fun get(key: K): V? {
|
fun get(key: K): V? {
|
||||||
val softRef = cache.get(key)
|
val softRef = cache.get(key) ?: return null
|
||||||
if (softRef == null) return null
|
|
||||||
val value = softRef.get()
|
val value = softRef.get()
|
||||||
|
|
||||||
return if (value != null) {
|
return if (value != null) {
|
||||||
@@ -99,7 +98,7 @@ class LargeSoftCache<K, V> : CacheOperations<K, V> {
|
|||||||
// another thread may put in between
|
// another thread may put in between
|
||||||
cache.remove(key, softRef)
|
cache.remove(key, softRef)
|
||||||
val newObject = builder(key)
|
val newObject = builder(key)
|
||||||
return cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject
|
cache.putIfAbsent(key, WeakReference(newObject))?.get() ?: newObject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,7 +134,7 @@ class LargeSoftCache<K, V> : CacheOperations<K, V> {
|
|||||||
.forEach(BiConsumerWrapper(this, consumer))
|
.forEach(BiConsumerWrapper(this, consumer))
|
||||||
}
|
}
|
||||||
|
|
||||||
class BiConsumerWrapper<K, V>(
|
class BiConsumerWrapper<K : Any, V : Any>(
|
||||||
val cache: LargeSoftCache<K, V>,
|
val cache: LargeSoftCache<K, V>,
|
||||||
val inner: BiConsumer<K, V>,
|
val inner: BiConsumer<K, V>,
|
||||||
) : BiConsumer<K, WeakReference<V>> {
|
) : BiConsumer<K, WeakReference<V>> {
|
||||||
|
|||||||
@@ -2770,10 +2770,9 @@ object LocalCache : ILocalCache, ICacheProvider {
|
|||||||
val childrenToBeRemoved = mutableListOf<Note>()
|
val childrenToBeRemoved = mutableListOf<Note>()
|
||||||
|
|
||||||
val toBeRemoved =
|
val toBeRemoved =
|
||||||
account.hiddenUsers.flow.value.hiddenUsers
|
account.hiddenUsers.flow.value.hiddenUsers.flatMap { userHex ->
|
||||||
.map { userHex ->
|
(notes.filter { _, it -> it.event?.pubKey == userHex } + addressables.filter { _, it -> it.event?.pubKey == userHex }).toSet()
|
||||||
(notes.filter { _, it -> it.event?.pubKey == userHex } + addressables.filter { _, it -> it.event?.pubKey == userHex }).toSet()
|
}
|
||||||
}.flatten()
|
|
||||||
|
|
||||||
toBeRemoved.forEach {
|
toBeRemoved.forEach {
|
||||||
removeFromCache(it)
|
removeFromCache(it)
|
||||||
|
|||||||
+4
-4
@@ -34,9 +34,9 @@ class TorAwareOkHttpOtsResolverBuilder(
|
|||||||
) : OtsResolverBuilder {
|
) : OtsResolverBuilder {
|
||||||
fun getAPI(usingTor: Boolean) =
|
fun getAPI(usingTor: Boolean) =
|
||||||
if (usingTor) {
|
if (usingTor) {
|
||||||
OkHttpBitcoinExplorer.Companion.MEMPOOL_API_URL
|
OkHttpBitcoinExplorer.MEMPOOL_API_URL
|
||||||
} else {
|
} else {
|
||||||
OkHttpBitcoinExplorer.Companion.BLOCKSTREAM_API_URL
|
OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun build(): OtsResolver =
|
override fun build(): OtsResolver =
|
||||||
@@ -44,9 +44,9 @@ class TorAwareOkHttpOtsResolverBuilder(
|
|||||||
explorer =
|
explorer =
|
||||||
OkHttpBitcoinExplorer(
|
OkHttpBitcoinExplorer(
|
||||||
baseUrl = {
|
baseUrl = {
|
||||||
getAPI(usingTor = isTorActive(OkHttpBitcoinExplorer.Companion.MEMPOOL_API_URL))
|
getAPI(usingTor = isTorActive(OkHttpBitcoinExplorer.MEMPOOL_API_URL))
|
||||||
},
|
},
|
||||||
client = okHttpClient(OkHttpBitcoinExplorer.Companion.MEMPOOL_API_URL),
|
client = okHttpClient(OkHttpBitcoinExplorer.MEMPOOL_API_URL),
|
||||||
cache = cache,
|
cache = cache,
|
||||||
),
|
),
|
||||||
calendar = OkHttpCalendar(okHttpClient),
|
calendar = OkHttpCalendar(okHttpClient),
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ class BookmarkListState(
|
|||||||
|
|
||||||
fun getBookmarkList(): BookmarkListEvent? = bookmarkList.event as? BookmarkListEvent
|
fun getBookmarkList(): BookmarkListEvent? = bookmarkList.event as? BookmarkListEvent
|
||||||
|
|
||||||
suspend fun publicBookmarks(note: Note): List<BookmarkIdTag> {
|
fun publicBookmarks(note: Note): List<BookmarkIdTag> {
|
||||||
val noteEvent = note.event as? BookmarkListEvent
|
val noteEvent = note.event as? BookmarkListEvent
|
||||||
return noteEvent?.publicBookmarks() ?: emptyList()
|
return noteEvent?.publicBookmarks() ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ class HiddenUsersState(
|
|||||||
) {
|
) {
|
||||||
var transientHiddenUsers: MutableStateFlow<Set<String>> = MutableStateFlow(setOf())
|
var transientHiddenUsers: MutableStateFlow<Set<String>> = MutableStateFlow(setOf())
|
||||||
|
|
||||||
suspend fun assembleLiveHiddenUsers(
|
fun assembleLiveHiddenUsers(
|
||||||
blockList: List<MuteTag>,
|
blockList: List<MuteTag>,
|
||||||
muteList: List<MuteTag>,
|
muteList: List<MuteTag>,
|
||||||
transientHiddenUsers: Set<String>,
|
transientHiddenUsers: Set<String>,
|
||||||
|
|||||||
+4
-4
@@ -34,9 +34,9 @@ data class LabeledBookmarkList(
|
|||||||
val privateBookmarks: Set<BookmarkIdTag> = emptySet(),
|
val privateBookmarks: Set<BookmarkIdTag> = emptySet(),
|
||||||
val publicBookmarks: Set<BookmarkIdTag> = emptySet(),
|
val publicBookmarks: Set<BookmarkIdTag> = emptySet(),
|
||||||
) {
|
) {
|
||||||
val privatePostBookmarks = privateBookmarks.filter { it is EventBookmark }.map { bookmarkIdTag -> bookmarkIdTag as EventBookmark }
|
val privatePostBookmarks = privateBookmarks.filterIsInstance<EventBookmark>()
|
||||||
val publicPostBookmarks = publicBookmarks.filter { it is EventBookmark }.map { bookmarkIdTag -> bookmarkIdTag as EventBookmark }
|
val publicPostBookmarks = publicBookmarks.filterIsInstance<EventBookmark>()
|
||||||
|
|
||||||
val privateArticleBookmarks = privateBookmarks.filter { it is AddressBookmark }.map { bookmarkIdTag -> bookmarkIdTag as AddressBookmark }
|
val privateArticleBookmarks = privateBookmarks.filterIsInstance<AddressBookmark>()
|
||||||
val publicArticleBookmarks = publicBookmarks.filter { it is AddressBookmark }.map { bookmarkIdTag -> bookmarkIdTag as AddressBookmark }
|
val publicArticleBookmarks = publicBookmarks.filterIsInstance<AddressBookmark>()
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -51,7 +51,7 @@ class SearchRelayListState(
|
|||||||
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
|
// Creates a long-term reference for this note so that the GC doesn't collect the note it self
|
||||||
val searchListNote = cache.getOrCreateAddressableNote(getSearchRelayListAddress())
|
val searchListNote = cache.getOrCreateAddressableNote(getSearchRelayListAddress())
|
||||||
|
|
||||||
fun getSearchRelayListAddress() = SearchRelayListEvent.Companion.createAddress(signer.pubKey)
|
fun getSearchRelayListAddress() = SearchRelayListEvent.createAddress(signer.pubKey)
|
||||||
|
|
||||||
fun getSearchRelayListFlow(): StateFlow<NoteState> = searchListNote.flow().metadata.stateFlow
|
fun getSearchRelayListFlow(): StateFlow<NoteState> = searchListNote.flow().metadata.stateFlow
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class SearchRelayListState(
|
|||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope,
|
scope,
|
||||||
SharingStarted.Companion.Eagerly,
|
SharingStarted.Eagerly,
|
||||||
emptySet(),
|
emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -68,7 +68,7 @@ class TrustedRelayListState(
|
|||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope,
|
scope,
|
||||||
SharingStarted.Companion.Eagerly,
|
SharingStarted.Eagerly,
|
||||||
emptySet(),
|
emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,13 +76,13 @@ class TrustedRelayListState(
|
|||||||
val relayListForTrusted = getTrustedRelayList()
|
val relayListForTrusted = getTrustedRelayList()
|
||||||
|
|
||||||
return if (relayListForTrusted != null && relayListForTrusted.tags.isNotEmpty()) {
|
return if (relayListForTrusted != null && relayListForTrusted.tags.isNotEmpty()) {
|
||||||
TrustedRelayListEvent.Companion.updateRelayList(
|
TrustedRelayListEvent.updateRelayList(
|
||||||
earlierVersion = relayListForTrusted,
|
earlierVersion = relayListForTrusted,
|
||||||
relays = trustedRelays,
|
relays = trustedRelays,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
TrustedRelayListEvent.Companion.create(
|
TrustedRelayListEvent.create(
|
||||||
relays = trustedRelays,
|
relays = trustedRelays,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -120,5 +120,5 @@ class BlossomServerListState(
|
|||||||
suspend fun createBlossomDeleteAuth(
|
suspend fun createBlossomDeleteAuth(
|
||||||
hash: HexKey,
|
hash: HexKey,
|
||||||
alt: String,
|
alt: String,
|
||||||
): BlossomAuthorizationEvent? = BlossomAuthorizationEvent.createDeleteAuth(hash, alt, signer)
|
): BlossomAuthorizationEvent = BlossomAuthorizationEvent.createDeleteAuth(hash, alt, signer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ import kotlinx.coroutines.flow.catch
|
|||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
suspend fun <T> DataStore<Preferences>.getProperty(
|
fun <T> DataStore<Preferences>.getProperty(
|
||||||
key: Preferences.Key<String>,
|
key: Preferences.Key<String>,
|
||||||
parser: (String) -> T,
|
parser: (String) -> T,
|
||||||
serializer: (T) -> String,
|
serializer: (T) -> String,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
): UpdatablePropertyFlow<T> =
|
): UpdatablePropertyFlow<T> =
|
||||||
UpdatablePropertyFlow<T>(
|
UpdatablePropertyFlow(
|
||||||
flow =
|
flow =
|
||||||
data
|
data
|
||||||
.catch { e ->
|
.catch { e ->
|
||||||
|
|||||||
+2
-2
@@ -67,12 +67,12 @@ class EncryptedDataStore(
|
|||||||
?.get(key)
|
?.get(key)
|
||||||
?.let { decrypt(it) }
|
?.let { decrypt(it) }
|
||||||
|
|
||||||
suspend fun <T> getProperty(
|
fun <T> getProperty(
|
||||||
key: Preferences.Key<String>,
|
key: Preferences.Key<String>,
|
||||||
parser: (String) -> T,
|
parser: (String) -> T,
|
||||||
serializer: (T) -> String,
|
serializer: (T) -> String,
|
||||||
): UpdatablePropertyFlow<T> =
|
): UpdatablePropertyFlow<T> =
|
||||||
UpdatablePropertyFlow<T>(
|
UpdatablePropertyFlow(
|
||||||
flow =
|
flow =
|
||||||
store.data
|
store.data
|
||||||
.catch { e ->
|
.catch { e ->
|
||||||
|
|||||||
+2
-2
@@ -60,8 +60,8 @@ class MergedFollowListsState(
|
|||||||
val geotags: Set<String> = emptySet(),
|
val geotags: Set<String> = emptySet(),
|
||||||
val communities: Set<String> = emptySet(),
|
val communities: Set<String> = emptySet(),
|
||||||
) {
|
) {
|
||||||
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf<String>()) { GeohashId.toScope(it) }
|
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf()) { GeohashId.toScope(it) }
|
||||||
val hashtagScopes: Set<String> = hashtags.mapTo(mutableSetOf<String>()) { HashtagId.toScope(it) }
|
val hashtagScopes: Set<String> = hashtags.mapTo(mutableSetOf()) { HashtagId.toScope(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mergeLists(
|
fun mergeLists(
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ class OutboxLoaderState(
|
|||||||
}.flowOn(Dispatchers.IO)
|
}.flowOn(Dispatchers.IO)
|
||||||
.stateIn(
|
.stateIn(
|
||||||
scope,
|
scope,
|
||||||
SharingStarted.Companion.Eagerly,
|
SharingStarted.Eagerly,
|
||||||
UnknownTopNavPerRelayFilterSet,
|
UnknownTopNavPerRelayFilterSet,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -52,8 +52,8 @@ class AllFollowsByOutboxTopNavFilter(
|
|||||||
val defaultRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
val defaultRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||||
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
val blockedRelays: StateFlow<Set<NormalizedRelayUrl>>,
|
||||||
) : IFeedTopNavFilter {
|
) : IFeedTopNavFilter {
|
||||||
val geotagScopes: Set<String>? = geotags?.mapTo(mutableSetOf<String>()) { GeohashId.Companion.toScope(it) }
|
val geotagScopes: Set<String>? = geotags?.mapTo(mutableSetOf()) { GeohashId.toScope(it) }
|
||||||
val hashtagScopes: Set<String>? = hashtags?.mapTo(mutableSetOf<String>()) { HashtagId.Companion.toScope(it) }
|
val hashtagScopes: Set<String>? = hashtags?.mapTo(mutableSetOf()) { HashtagId.toScope(it) }
|
||||||
|
|
||||||
override fun matchAuthor(pubkey: HexKey): Boolean = authors == null || pubkey in authors
|
override fun matchAuthor(pubkey: HexKey): Boolean = authors == null || pubkey in authors
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -47,8 +47,8 @@ class AllFollowsByProxyTopNavFilter(
|
|||||||
val communities: Set<String>? = null,
|
val communities: Set<String>? = null,
|
||||||
val proxyRelays: Set<NormalizedRelayUrl>,
|
val proxyRelays: Set<NormalizedRelayUrl>,
|
||||||
) : IFeedTopNavFilter {
|
) : IFeedTopNavFilter {
|
||||||
val geotagScopes: Set<String>? = geotags?.mapTo(mutableSetOf<String>()) { GeohashId.Companion.toScope(it) }
|
val geotagScopes: Set<String>? = geotags?.mapTo(mutableSetOf()) { GeohashId.toScope(it) }
|
||||||
val hashtagScopes: Set<String>? = hashtags?.mapTo(mutableSetOf<String>()) { HashtagId.Companion.toScope(it) }
|
val hashtagScopes: Set<String>? = hashtags?.mapTo(mutableSetOf()) { HashtagId.toScope(it) }
|
||||||
|
|
||||||
override fun matchAuthor(pubkey: HexKey): Boolean = authors == null || pubkey in authors
|
override fun matchAuthor(pubkey: HexKey): Boolean = authors == null || pubkey in authors
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -35,6 +35,6 @@ class AllFollowsTopNavPerRelayFilter(
|
|||||||
val geotags: Set<String>? = null,
|
val geotags: Set<String>? = null,
|
||||||
val communities: Set<String>? = null,
|
val communities: Set<String>? = null,
|
||||||
) : IFeedTopNavPerRelayFilter {
|
) : IFeedTopNavPerRelayFilter {
|
||||||
val geotagScopes: Set<String>? = geotags?.mapTo(mutableSetOf<String>()) { GeohashId.toScope(it) }
|
val geotagScopes: Set<String>? = geotags?.mapTo(mutableSetOf()) { GeohashId.toScope(it) }
|
||||||
val hashtagScopes: Set<String>? = hashtags?.mapTo(mutableSetOf<String>()) { HashtagId.toScope(it) }
|
val hashtagScopes: Set<String>? = hashtags?.mapTo(mutableSetOf()) { HashtagId.toScope(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ class LocationTopNavFilter(
|
|||||||
val geotags: Set<String>,
|
val geotags: Set<String>,
|
||||||
val relayList: Set<NormalizedRelayUrl>,
|
val relayList: Set<NormalizedRelayUrl>,
|
||||||
) : IFeedTopNavFilter {
|
) : IFeedTopNavFilter {
|
||||||
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf<String>()) { GeohashId.toScope(it) }
|
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf()) { GeohashId.toScope(it) }
|
||||||
|
|
||||||
override fun matchAuthor(pubkey: HexKey): Boolean = true
|
override fun matchAuthor(pubkey: HexKey): Boolean = true
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -28,5 +28,5 @@ import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId
|
|||||||
class LocationTopNavPerRelayFilter(
|
class LocationTopNavPerRelayFilter(
|
||||||
val geotags: Set<String>,
|
val geotags: Set<String>,
|
||||||
) : IFeedTopNavPerRelayFilter {
|
) : IFeedTopNavPerRelayFilter {
|
||||||
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf<String>()) { GeohashId.toScope(it) }
|
val geotagScopes: Set<String> = geotags.mapTo(mutableSetOf()) { GeohashId.toScope(it) }
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -62,7 +62,7 @@ class NoteFeedFlow(
|
|||||||
): IFeedTopNavFilter =
|
): IFeedTopNavFilter =
|
||||||
when (noteEvent) {
|
when (noteEvent) {
|
||||||
is PeopleListEvent -> {
|
is PeopleListEvent -> {
|
||||||
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
|
if (noteEvent.dTag() == PeopleListEvent.BLOCK_LIST_D_TAG) {
|
||||||
MutedAuthorsByOutboxTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), blockedRelays)
|
MutedAuthorsByOutboxTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), blockedRelays)
|
||||||
} else {
|
} else {
|
||||||
AuthorsByOutboxTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), blockedRelays)
|
AuthorsByOutboxTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), blockedRelays)
|
||||||
@@ -109,7 +109,7 @@ class NoteFeedFlow(
|
|||||||
) {
|
) {
|
||||||
when (noteEvent) {
|
when (noteEvent) {
|
||||||
is PeopleListEvent -> {
|
is PeopleListEvent -> {
|
||||||
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
|
if (noteEvent.dTag() == PeopleListEvent.BLOCK_LIST_D_TAG) {
|
||||||
emit(MutedAuthorsByOutboxTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), blockedRelays))
|
emit(MutedAuthorsByOutboxTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), blockedRelays))
|
||||||
} else {
|
} else {
|
||||||
emit(AuthorsByOutboxTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), blockedRelays))
|
emit(AuthorsByOutboxTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), blockedRelays))
|
||||||
@@ -161,7 +161,7 @@ class NoteFeedFlow(
|
|||||||
): IFeedTopNavFilter =
|
): IFeedTopNavFilter =
|
||||||
when (noteEvent) {
|
when (noteEvent) {
|
||||||
is PeopleListEvent -> {
|
is PeopleListEvent -> {
|
||||||
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
|
if (noteEvent.dTag() == PeopleListEvent.BLOCK_LIST_D_TAG) {
|
||||||
MutedAuthorsByProxyTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), proxyRelays)
|
MutedAuthorsByProxyTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), proxyRelays)
|
||||||
} else {
|
} else {
|
||||||
AuthorsByProxyTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), proxyRelays)
|
AuthorsByProxyTopNavFilter(caches.peopleListCache.cachedUserIdSet(noteEvent), proxyRelays)
|
||||||
@@ -208,7 +208,7 @@ class NoteFeedFlow(
|
|||||||
) {
|
) {
|
||||||
when (noteEvent) {
|
when (noteEvent) {
|
||||||
is PeopleListEvent -> {
|
is PeopleListEvent -> {
|
||||||
if (noteEvent.dTag() == PeopleListEvent.Companion.BLOCK_LIST_D_TAG) {
|
if (noteEvent.dTag() == PeopleListEvent.BLOCK_LIST_D_TAG) {
|
||||||
emit(MutedAuthorsByProxyTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), proxyRelays))
|
emit(MutedAuthorsByProxyTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), proxyRelays))
|
||||||
} else {
|
} else {
|
||||||
emit(AuthorsByProxyTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), proxyRelays))
|
emit(AuthorsByProxyTopNavFilter(caches.peopleListCache.userIdSet(noteEvent), proxyRelays))
|
||||||
@@ -272,7 +272,7 @@ class NoteFeedFlow(
|
|||||||
override fun startValue(): IFeedTopNavFilter {
|
override fun startValue(): IFeedTopNavFilter {
|
||||||
val noteEvent = metadataFlow.value?.note?.event
|
val noteEvent = metadataFlow.value?.note?.event
|
||||||
return if (noteEvent == null) {
|
return if (noteEvent == null) {
|
||||||
return AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays)
|
AuthorsByOutboxTopNavFilter(emptySet(), blockedRelays)
|
||||||
} else {
|
} else {
|
||||||
if (proxyRelays.value.isEmpty()) {
|
if (proxyRelays.value.isEmpty()) {
|
||||||
processByOutbox(noteEvent, outboxRelays.value)
|
processByOutbox(noteEvent, outboxRelays.value)
|
||||||
|
|||||||
+1
-1
@@ -83,7 +83,7 @@ class MeltProcessor {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun melt(
|
fun melt(
|
||||||
token: CashuToken,
|
token: CashuToken,
|
||||||
lud16: String,
|
lud16: String,
|
||||||
okHttpClient: (String) -> OkHttpClient,
|
okHttpClient: (String) -> OkHttpClient,
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ class MemoryTrimmingService(
|
|||||||
) {
|
) {
|
||||||
var isTrimmingMemoryMutex = AtomicBoolean(false)
|
var isTrimmingMemoryMutex = AtomicBoolean(false)
|
||||||
|
|
||||||
private suspend fun doTrim(
|
private fun doTrim(
|
||||||
account: Collection<Account>,
|
account: Collection<Account>,
|
||||||
otherAccounts: List<AccountInfo>,
|
otherAccounts: List<AccountInfo>,
|
||||||
) {
|
) {
|
||||||
|
|||||||
+4
-5
@@ -65,14 +65,11 @@ class LightningAddressResolver {
|
|||||||
okHttpClient: (String) -> OkHttpClient,
|
okHttpClient: (String) -> OkHttpClient,
|
||||||
context: Context,
|
context: Context,
|
||||||
): String {
|
): String {
|
||||||
val url = assembleUrl(lnAddress)
|
val url =
|
||||||
|
assembleUrl(lnAddress) ?: throw LightningAddressError(
|
||||||
if (url == null) {
|
|
||||||
throw LightningAddressError(
|
|
||||||
stringRes(context, R.string.error_unable_to_fetch_invoice),
|
stringRes(context, R.string.error_unable_to_fetch_invoice),
|
||||||
stringRes(context, R.string.could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup, lnAddress),
|
stringRes(context, R.string.could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup, lnAddress),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val client = okHttpClient(url)
|
val client = okHttpClient(url)
|
||||||
|
|
||||||
@@ -126,12 +123,14 @@ class LightningAddressResolver {
|
|||||||
okHttpClient: (String) -> OkHttpClient,
|
okHttpClient: (String) -> OkHttpClient,
|
||||||
context: Context,
|
context: Context,
|
||||||
): String {
|
): String {
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext") // URLEncoder.encode is CPU-only, not I/O blocking
|
||||||
val encodedMessage = URLEncoder.encode(message, "utf-8")
|
val encodedMessage = URLEncoder.encode(message, "utf-8")
|
||||||
|
|
||||||
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
|
val urlBinder = if (lnCallback.contains("?")) "&" else "?"
|
||||||
var url = "$lnCallback${urlBinder}amount=$milliSats&comment=$encodedMessage"
|
var url = "$lnCallback${urlBinder}amount=$milliSats&comment=$encodedMessage"
|
||||||
|
|
||||||
if (nostrRequest != null) {
|
if (nostrRequest != null) {
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext") // URLEncoder.encode is CPU-only, not I/O blocking
|
||||||
val encodedNostrRequest = URLEncoder.encode(nostrRequest.toJson(), "utf-8")
|
val encodedNostrRequest = URLEncoder.encode(nostrRequest.toJson(), "utf-8")
|
||||||
url += "&nostr=$encodedNostrRequest"
|
url += "&nostr=$encodedNostrRequest"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class LocationState(
|
|||||||
object Loading : LocationResult()
|
object Loading : LocationResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var hasLocationPermission = MutableStateFlow<Boolean>(false)
|
private var hasLocationPermission = MutableStateFlow(false)
|
||||||
private var latestLocation: LocationResult = LocationResult.Loading
|
private var latestLocation: LocationResult = LocationResult.Loading
|
||||||
|
|
||||||
fun setLocationPermission(newValue: Boolean) {
|
fun setLocationPermission(newValue: Boolean) {
|
||||||
|
|||||||
+13
-4
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.namecoin
|
package com.vitorpamplona.amethyst.service.namecoin
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.DEFAULT_ELECTRUMX_SERVERS
|
||||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumXClient
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumXClient
|
||||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer
|
||||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinLookupCache
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinLookupCache
|
||||||
@@ -43,12 +44,17 @@ class NamecoinNameService(
|
|||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
private val resolver = NamecoinNameResolver(electrumxClient)
|
|
||||||
private val cache = NamecoinLookupCache()
|
|
||||||
|
|
||||||
// Custom server list (user-configurable)
|
// Custom server list (user-configurable)
|
||||||
|
@Volatile
|
||||||
private var customServers: List<ElectrumxServer> = emptyList()
|
private var customServers: List<ElectrumxServer> = emptyList()
|
||||||
|
|
||||||
|
private val resolver =
|
||||||
|
NamecoinNameResolver(
|
||||||
|
electrumxClient = electrumxClient,
|
||||||
|
serverListProvider = { customServers.ifEmpty { DEFAULT_ELECTRUMX_SERVERS } },
|
||||||
|
)
|
||||||
|
private val cache = NamecoinLookupCache()
|
||||||
|
|
||||||
// ── Public API ─────────────────────────────────────────────────────
|
// ── Public API ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,7 +103,10 @@ class NamecoinNameService(
|
|||||||
*
|
*
|
||||||
* Useful for composable UIs that observe resolution state.
|
* Useful for composable UIs that observe resolution state.
|
||||||
*/
|
*/
|
||||||
fun resolveLive(identifier: String): StateFlow<NamecoinResolveState> {
|
fun resolveLive(
|
||||||
|
identifier: String,
|
||||||
|
scope: CoroutineScope = this.scope,
|
||||||
|
): StateFlow<NamecoinResolveState> {
|
||||||
val state = MutableStateFlow<NamecoinResolveState>(NamecoinResolveState.Loading)
|
val state = MutableStateFlow<NamecoinResolveState>(NamecoinResolveState.Loading)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+2
-1
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.okhttp
|
|||||||
import okhttp3.internal.concurrent.TaskRunner
|
import okhttp3.internal.concurrent.TaskRunner
|
||||||
import okhttp3.internal.http2.Http2
|
import okhttp3.internal.http2.Http2
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
import java.util.Locale
|
||||||
import java.util.concurrent.CopyOnWriteArraySet
|
import java.util.concurrent.CopyOnWriteArraySet
|
||||||
import java.util.logging.ConsoleHandler
|
import java.util.logging.ConsoleHandler
|
||||||
import java.util.logging.Handler
|
import java.util.logging.Handler
|
||||||
@@ -45,7 +46,7 @@ object OkHttpDebugLogging {
|
|||||||
level = Level.FINE
|
level = Level.FINE
|
||||||
formatter =
|
formatter =
|
||||||
object : SimpleFormatter() {
|
object : SimpleFormatter() {
|
||||||
override fun format(record: LogRecord) = String.format("[%1\$tF %1\$tT] %2\$s %n", record.millis, record.message)
|
override fun format(record: LogRecord) = String.format(Locale.ROOT, $$"[%1$tF %1$tT] %2$s %n", record.millis, record.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,7 @@ class OkHttpWebSocket(
|
|||||||
override fun needsReconnect(): Boolean {
|
override fun needsReconnect(): Boolean {
|
||||||
if (socket == null) return true
|
if (socket == null) return true
|
||||||
|
|
||||||
val myUsingOkHttp = usingOkHttp
|
val myUsingOkHttp = usingOkHttp ?: return true
|
||||||
if (myUsingOkHttp == null) return true
|
|
||||||
|
|
||||||
val currentOkHttp = httpClient(url)
|
val currentOkHttp = httpClient(url)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -102,7 +102,7 @@ fun VideoView(
|
|||||||
) {
|
) {
|
||||||
val automaticallyStartPlayback =
|
val automaticallyStartPlayback =
|
||||||
remember {
|
remember {
|
||||||
mutableStateOf<Boolean>(
|
mutableStateOf(
|
||||||
if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback(),
|
if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPla
|
|||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
public val DEFAULT_MUTED_SETTING = mutableStateOf(true)
|
val DEFAULT_MUTED_SETTING = mutableStateOf(true)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoViewInner(
|
fun VideoViewInner(
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ fun MuteButton(
|
|||||||
) {
|
) {
|
||||||
val holdOn =
|
val holdOn =
|
||||||
remember {
|
remember {
|
||||||
mutableStateOf<Boolean>(
|
mutableStateOf(
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -77,7 +77,7 @@ class IntentExtras {
|
|||||||
data.mimeType?.let { putString("mimeType", it) }
|
data.mimeType?.let { putString("mimeType", it) }
|
||||||
data.aspectRatio?.let { putFloat("aspectRatio", it) }
|
data.aspectRatio?.let { putFloat("aspectRatio", it) }
|
||||||
data.proxyPort?.let { putInt("proxyPort", it) }
|
data.proxyPort?.let { putInt("proxyPort", it) }
|
||||||
data.keepPlaying.let { putBoolean("keepPlaying", it) }
|
putBoolean("keepPlaying", data.keepPlaying)
|
||||||
data.waveformData?.let { putFloatArray("wavefrontData", it.wave.toFloatArray()) }
|
data.waveformData?.let { putFloatArray("wavefrontData", it.wave.toFloatArray()) }
|
||||||
|
|
||||||
bounds?.let { putInt("boundLeft", it.left) }
|
bounds?.let { putInt("boundLeft", it.left) }
|
||||||
|
|||||||
+3
-9
@@ -20,8 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.playback.pip
|
package com.vitorpamplona.amethyst.service.playback.pip
|
||||||
|
|
||||||
import android.content.Context.RECEIVER_EXPORTED
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -34,6 +32,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.ui.compose.ContentFrame
|
import androidx.media3.ui.compose.ContentFrame
|
||||||
import androidx.media3.ui.compose.state.rememberMuteButtonState
|
import androidx.media3.ui.compose.state.rememberMuteButtonState
|
||||||
@@ -91,13 +90,8 @@ fun RegisterControllerReceiver(controllerState: MediaControllerState) {
|
|||||||
onPlayPause = controllerState::togglePlayPause,
|
onPlayPause = controllerState::togglePlayPause,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
ContextCompat.registerReceiver(context, receiver, receiver.filterMute, ContextCompat.RECEIVER_EXPORTED)
|
||||||
context.registerReceiver(receiver, receiver.filterMute, RECEIVER_EXPORTED)
|
ContextCompat.registerReceiver(context, receiver, receiver.filterPlayPause, ContextCompat.RECEIVER_EXPORTED)
|
||||||
context.registerReceiver(receiver, receiver.filterPlayPause, RECEIVER_EXPORTED)
|
|
||||||
} else {
|
|
||||||
context.registerReceiver(receiver, receiver.filterMute)
|
|
||||||
context.registerReceiver(receiver, receiver.filterPlayPause)
|
|
||||||
}
|
|
||||||
|
|
||||||
onDispose {
|
onDispose {
|
||||||
context.unregisterReceiver(receiver)
|
context.unregisterReceiver(receiver)
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ import okhttp3.OkHttpClient
|
|||||||
class ExoPlayerBuilder(
|
class ExoPlayerBuilder(
|
||||||
val okHttp: OkHttpClient,
|
val okHttp: OkHttpClient,
|
||||||
) {
|
) {
|
||||||
fun build(context: Context) =
|
fun build(context: Context): ExoPlayer =
|
||||||
ExoPlayer
|
ExoPlayer
|
||||||
.Builder(context)
|
.Builder(context)
|
||||||
.apply {
|
.apply {
|
||||||
|
|||||||
+3
-3
@@ -26,7 +26,7 @@ class ListWithUniqueSetCache<T, U>(
|
|||||||
val key: (T) -> U,
|
val key: (T) -> U,
|
||||||
) {
|
) {
|
||||||
private val list = AtomicReference(listOf<T>())
|
private val list = AtomicReference(listOf<T>())
|
||||||
private val cacheSet = AtomicReference<Set<U>?>(setOf<U>())
|
private val cacheSet = AtomicReference<Set<U>?>(setOf())
|
||||||
|
|
||||||
fun isEmpty() = list.get().isEmpty()
|
fun isEmpty() = list.get().isEmpty()
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class ListWithUniqueSetCache<T, U>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun distinct(): Set<U> {
|
fun distinct(): Set<U> {
|
||||||
var currentSet = cacheSet.get()
|
val currentSet = cacheSet.get()
|
||||||
|
|
||||||
// Check if the cached set is based on the current list
|
// Check if the cached set is based on the current list
|
||||||
if (currentSet != null) {
|
if (currentSet != null) {
|
||||||
@@ -49,7 +49,7 @@ class ListWithUniqueSetCache<T, U>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute and attempt to atomically update the cache
|
// Compute and attempt to atomically update the cache
|
||||||
val newSet = list.get().mapTo(mutableSetOf<U>(), key)
|
val newSet = list.get().mapTo(mutableSetOf(), key)
|
||||||
cacheSet.compareAndSet(currentSet, newSet)
|
cacheSet.compareAndSet(currentSet, newSet)
|
||||||
return newSet
|
return newSet
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManagerControls
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.account.AccountFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderFilterAssemblyGroup
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderFilterAssemblyGroup
|
||||||
@@ -88,7 +87,7 @@ class RelaySubscriptionsCoordinator(
|
|||||||
val nwc = NWCPaymentFilterAssembler(client)
|
val nwc = NWCPaymentFilterAssembler(client)
|
||||||
|
|
||||||
val all =
|
val all =
|
||||||
listOf<ComposeSubscriptionManagerControls>(
|
listOf(
|
||||||
account,
|
account,
|
||||||
home,
|
home,
|
||||||
chatroomList,
|
chatroomList,
|
||||||
|
|||||||
+1
-1
@@ -36,7 +36,7 @@ fun filterDraftsFromKey(
|
|||||||
pubkey: HexKey?,
|
pubkey: HexKey?,
|
||||||
since: Long?,
|
since: Long?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
if (pubkey == null || pubkey.isEmpty()) return emptyList()
|
if (pubkey.isNullOrEmpty()) return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ class AccountFollowsLoaderSubAssembler(
|
|||||||
* This assembler saves the EOSE per user key. That EOSE includes their metadata, etc
|
* This assembler saves the EOSE per user key. That EOSE includes their metadata, etc
|
||||||
* and reports, but only from trusted accounts (follows of all logged in users).
|
* and reports, but only from trusted accounts (follows of all logged in users).
|
||||||
*/
|
*/
|
||||||
val hasTried: EOSEAccountFast<User> = EOSEAccountFast<User>(2000)
|
val hasTried: EOSEAccountFast<User> = EOSEAccountFast(2000)
|
||||||
|
|
||||||
// updates all filters
|
// updates all filters
|
||||||
override fun invalidateFilters(ignoreIfDoing: Boolean) {
|
override fun invalidateFilters(ignoreIfDoing: Boolean) {
|
||||||
|
|||||||
+1
-1
@@ -92,7 +92,7 @@ fun pickRelaysToLoadUsers(
|
|||||||
|
|
||||||
val outbox = key.authorRelayList()?.writeRelaysNorm()
|
val outbox = key.authorRelayList()?.writeRelaysNorm()
|
||||||
|
|
||||||
if (outbox != null && outbox.isNotEmpty()) {
|
if (!outbox.isNullOrEmpty()) {
|
||||||
// If there is a home, get from it.
|
// If there is a home, get from it.
|
||||||
|
|
||||||
// if it tried all outbox relays, stop.
|
// if it tried all outbox relays, stop.
|
||||||
|
|||||||
+1
-1
@@ -70,7 +70,7 @@ fun filterBasicAccountInfoFromKeys(
|
|||||||
otherAccounts: List<HexKey>?,
|
otherAccounts: List<HexKey>?,
|
||||||
since: Long?,
|
since: Long?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
if (otherAccounts == null || otherAccounts.isEmpty()) return emptyList()
|
if (otherAccounts.isNullOrEmpty()) return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ fun filterBookmarksAndReportsFromKey(
|
|||||||
pubkey: HexKey?,
|
pubkey: HexKey?,
|
||||||
since: Long?,
|
since: Long?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
if (pubkey == null || pubkey.isEmpty()) return emptyList()
|
if (pubkey.isNullOrEmpty()) return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ class AccountNotificationsEoseFromInboxRelaysManager(
|
|||||||
override fun updateFilter(
|
override fun updateFilter(
|
||||||
key: AccountQueryState,
|
key: AccountQueryState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? =
|
): List<RelayBasedFilter> =
|
||||||
key.account.notificationRelays.flow.value.flatMap {
|
key.account.notificationRelays.flow.value.flatMap {
|
||||||
filterSummaryNotificationsToPubkey(
|
filterSummaryNotificationsToPubkey(
|
||||||
relay = it,
|
relay = it,
|
||||||
|
|||||||
+1
-1
@@ -50,7 +50,7 @@ class AccountNotificationsEoseFromRandomRelaysManager(
|
|||||||
override fun updateFilter(
|
override fun updateFilter(
|
||||||
key: AccountQueryState,
|
key: AccountQueryState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? {
|
): List<RelayBasedFilter> {
|
||||||
// only loads this after the feed is built
|
// only loads this after the feed is built
|
||||||
val defaultSince = key.feedContentStates.notifications.lastNoteCreatedAtIfFilled() ?: TimeUtils.oneWeekAgo()
|
val defaultSince = key.feedContentStates.notifications.lastNoteCreatedAtIfFilled() ?: TimeUtils.oneWeekAgo()
|
||||||
return (key.account.followsPerRelay.value.keys - key.account.notificationRelays.flow.value).flatMap {
|
return (key.account.followsPerRelay.value.keys - key.account.notificationRelays.flow.value).flatMap {
|
||||||
|
|||||||
+3
-3
@@ -90,7 +90,7 @@ fun filterSummaryNotificationsToPubkey(
|
|||||||
pubkey: HexKey?,
|
pubkey: HexKey?,
|
||||||
since: Long?,
|
since: Long?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
if (pubkey == null || pubkey.isEmpty()) return emptyList()
|
if (pubkey.isNullOrEmpty()) return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
@@ -111,7 +111,7 @@ fun filterNotificationsToPubkey(
|
|||||||
pubkey: HexKey?,
|
pubkey: HexKey?,
|
||||||
since: Long?,
|
since: Long?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
if (pubkey == null || pubkey.isEmpty()) return emptyList()
|
if (pubkey.isNullOrEmpty()) return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
@@ -142,7 +142,7 @@ fun filterJustTheLatestNotificationsToPubkeyFromRandomRelays(
|
|||||||
pubkey: HexKey?,
|
pubkey: HexKey?,
|
||||||
since: Long?,
|
since: Long?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
if (pubkey == null || pubkey.isEmpty()) return emptyList()
|
if (pubkey.isNullOrEmpty()) return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ fun filterGiftWrapsToPubkey(
|
|||||||
pubkey: HexKey?,
|
pubkey: HexKey?,
|
||||||
since: Long?,
|
since: Long?,
|
||||||
): List<RelayBasedFilter> {
|
): List<RelayBasedFilter> {
|
||||||
if (pubkey == null || pubkey.isEmpty()) return emptyList()
|
if (pubkey.isNullOrEmpty()) return emptyList()
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ class ChannelLoaderSubAssembler(
|
|||||||
client: INostrClient,
|
client: INostrClient,
|
||||||
allKeys: () -> Set<ChannelFinderQueryState>,
|
allKeys: () -> Set<ChannelFinderQueryState>,
|
||||||
) : SingleSubNoEoseCacheEoseManager<ChannelFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
) : SingleSubNoEoseCacheEoseManager<ChannelFinderQueryState>(client, allKeys, invalidateAfterEose = true) {
|
||||||
override fun updateFilter(keys: List<ChannelFinderQueryState>): List<RelayBasedFilter>? = filterMissingChannelsById(keys)
|
override fun updateFilter(keys: List<ChannelFinderQueryState>): List<RelayBasedFilter> = filterMissingChannelsById(keys)
|
||||||
|
|
||||||
override fun distinct(key: ChannelFinderQueryState) = key.channel
|
override fun distinct(key: ChannelFinderQueryState) = key.channel
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -40,7 +40,7 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
|
|||||||
|
|
||||||
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
||||||
|
|
||||||
note.replyTo?.map { parentNote ->
|
note.replyTo?.forEach { parentNote ->
|
||||||
set.addAll(parentNote.relays)
|
set.addAll(parentNote.relays)
|
||||||
|
|
||||||
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
||||||
@@ -48,7 +48,7 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
|
|||||||
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
|
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
note.replies.map { childNote ->
|
note.replies.forEach { childNote ->
|
||||||
set.addAll(childNote.relays)
|
set.addAll(childNote.relays)
|
||||||
|
|
||||||
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
||||||
@@ -63,7 +63,7 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
note.boosts.map { childNote ->
|
note.boosts.forEach { childNote ->
|
||||||
set.addAll(childNote.relays)
|
set.addAll(childNote.relays)
|
||||||
childNote.author?.outboxRelays()?.let { set.addAll(it) }
|
childNote.author?.outboxRelays()?.let { set.addAll(it) }
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ fun potentialRelaysToFindAddress(note: AddressableNote): Set<NormalizedRelayUrl>
|
|||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filterMissingAddressables(keys: List<EventFinderQueryState>): List<RelayBasedFilter>? {
|
fun filterMissingAddressables(keys: List<EventFinderQueryState>): List<RelayBasedFilter> {
|
||||||
val addressesPerRelay =
|
val addressesPerRelay =
|
||||||
mapOfSet {
|
mapOfSet {
|
||||||
keys.forEach { key ->
|
keys.forEach { key ->
|
||||||
|
|||||||
+4
-4
@@ -36,7 +36,7 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
|||||||
|
|
||||||
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
LocalCache.getAnyChannel(note)?.relays()?.let { set.addAll(it) }
|
||||||
|
|
||||||
note.replyTo?.map { parentNote ->
|
note.replyTo?.forEach { parentNote ->
|
||||||
set.addAll(parentNote.relays)
|
set.addAll(parentNote.relays)
|
||||||
|
|
||||||
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
LocalCache.getAnyChannel(parentNote)?.relays()?.let { set.addAll(it) }
|
||||||
@@ -44,7 +44,7 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
|||||||
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
|
parentNote.author?.inboxRelays()?.let { set.addAll(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
note.replies.map { childNote ->
|
note.replies.forEach { childNote ->
|
||||||
set.addAll(childNote.relays)
|
set.addAll(childNote.relays)
|
||||||
|
|
||||||
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
LocalCache.getAnyChannel(childNote)?.relays()?.let { set.addAll(it) }
|
||||||
@@ -59,7 +59,7 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
note.boosts.map { childNote ->
|
note.boosts.forEach { childNote ->
|
||||||
set.addAll(childNote.relays)
|
set.addAll(childNote.relays)
|
||||||
childNote.author?.outboxRelays()?.let { set.addAll(it) }
|
childNote.author?.outboxRelays()?.let { set.addAll(it) }
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ fun potentialRelaysToFindEvent(note: Note): Set<NormalizedRelayUrl> {
|
|||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
fun filterMissingEvents(keys: List<EventFinderQueryState>): List<RelayBasedFilter>? {
|
fun filterMissingEvents(keys: List<EventFinderQueryState>): List<RelayBasedFilter> {
|
||||||
val eventsPerRelay =
|
val eventsPerRelay =
|
||||||
mapOfSet {
|
mapOfSet {
|
||||||
keys.forEach { key ->
|
keys.forEach { key ->
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ class EventWatcherSubAssembler(
|
|||||||
allKeys: () -> Set<EventFinderQueryState>,
|
allKeys: () -> Set<EventFinderQueryState>,
|
||||||
) : SingleSubEoseManager<EventFinderQueryState>(client, allKeys) {
|
) : SingleSubEoseManager<EventFinderQueryState>(client, allKeys) {
|
||||||
var lastNotesOnFilter = emptyList<Note>()
|
var lastNotesOnFilter = emptyList<Note>()
|
||||||
var latestEOSEs: EOSEAccountFast<Note> = EOSEAccountFast<Note>(1000)
|
var latestEOSEs: EOSEAccountFast<Note> = EOSEAccountFast(1000)
|
||||||
|
|
||||||
override fun newEose(
|
override fun newEose(
|
||||||
relay: NormalizedRelayUrl,
|
relay: NormalizedRelayUrl,
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@ class UserOutboxFinderSubAssembler(
|
|||||||
* This assembler saves the EOSE per user key. That EOSE includes their metadata, etc
|
* This assembler saves the EOSE per user key. That EOSE includes their metadata, etc
|
||||||
* and reports, but only from trusted accounts (follows of all logged in users).
|
* and reports, but only from trusted accounts (follows of all logged in users).
|
||||||
*/
|
*/
|
||||||
var hasTried: EOSEAccountFast<User> = EOSEAccountFast<User>(200)
|
var hasTried: EOSEAccountFast<User> = EOSEAccountFast(200)
|
||||||
|
|
||||||
fun newEose(
|
fun newEose(
|
||||||
relay: NormalizedRelayUrl,
|
relay: NormalizedRelayUrl,
|
||||||
|
|||||||
+1
-1
@@ -68,7 +68,7 @@ class UserCardsSubAssembler(
|
|||||||
val trustedAccounts: Map<NormalizedRelayUrl, Set<HexKey>> =
|
val trustedAccounts: Map<NormalizedRelayUrl, Set<HexKey>> =
|
||||||
mapOfSet {
|
mapOfSet {
|
||||||
accounts.forEach { account ->
|
accounts.forEach { account ->
|
||||||
account.homeRelays.flow.value.map {
|
account.homeRelays.flow.value.forEach {
|
||||||
add(it, account.userProfile().pubkeyHex)
|
add(it, account.userProfile().pubkeyHex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -30,8 +30,8 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
|||||||
class KindGroup(
|
class KindGroup(
|
||||||
var count: AtomicInteger = AtomicInteger(0),
|
var count: AtomicInteger = AtomicInteger(0),
|
||||||
var memory: AtomicInteger = AtomicInteger(0),
|
var memory: AtomicInteger = AtomicInteger(0),
|
||||||
val subs: LargeCache<String, AtomicInteger> = LargeCache<String, AtomicInteger>(),
|
val subs: LargeCache<String, AtomicInteger> = LargeCache(),
|
||||||
val relays: LargeCache<NormalizedRelayUrl, AtomicInteger> = LargeCache<NormalizedRelayUrl, AtomicInteger>(),
|
val relays: LargeCache<NormalizedRelayUrl, AtomicInteger> = LargeCache(),
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
const val MB: Int = 1024
|
const val MB: Int = 1024
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ typealias MutableTime = com.vitorpamplona.amethyst.commons.relays.MutableTime
|
|||||||
open class EOSEByKey<U : Any>(
|
open class EOSEByKey<U : Any>(
|
||||||
cacheSize: Int = 200,
|
cacheSize: Int = 200,
|
||||||
) {
|
) {
|
||||||
var followList: LruCache<U, EOSERelayList> = LruCache<U, EOSERelayList>(cacheSize)
|
var followList: LruCache<U, EOSERelayList> = LruCache(cacheSize)
|
||||||
|
|
||||||
fun addOrUpdate(
|
fun addOrUpdate(
|
||||||
listCode: U,
|
listCode: U,
|
||||||
@@ -99,7 +99,7 @@ open class EOSEAccountKey<U : Any>(
|
|||||||
class EOSEAccountFast<T : Any>(
|
class EOSEAccountFast<T : Any>(
|
||||||
cacheSize: Int = 20,
|
cacheSize: Int = 20,
|
||||||
) {
|
) {
|
||||||
private val users: LruCache<T, EOSERelayList> = LruCache<T, EOSERelayList>(cacheSize)
|
private val users: LruCache<T, EOSERelayList> = LruCache(cacheSize)
|
||||||
private val lock = Any()
|
private val lock = Any()
|
||||||
|
|
||||||
fun addOrUpdate(
|
fun addOrUpdate(
|
||||||
|
|||||||
+7
-10
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.service.tts
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.AudioAttributes
|
import android.media.AudioAttributes
|
||||||
import android.os.Build
|
|
||||||
import android.speech.tts.TextToSpeech
|
import android.speech.tts.TextToSpeech
|
||||||
import android.speech.tts.UtteranceProgressListener
|
import android.speech.tts.UtteranceProgressListener
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -79,15 +78,13 @@ class TextToSpeechEngine private constructor() {
|
|||||||
engine.setPitch(defaultPitch)
|
engine.setPitch(defaultPitch)
|
||||||
engine.setSpeechRate(defaultSpeed)
|
engine.setSpeechRate(defaultSpeed)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
val audioAttributes =
|
||||||
val audioAttributes =
|
AudioAttributes
|
||||||
AudioAttributes
|
.Builder()
|
||||||
.Builder()
|
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
||||||
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
|
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
.build()
|
||||||
.build()
|
engine.setAudioAttributes(audioAttributes)
|
||||||
engine.setAudioAttributes(audioAttributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
engine.setListener(
|
engine.setListener(
|
||||||
onStart = { onStartListener?.invoke() },
|
onStart = { onStartListener?.invoke() },
|
||||||
|
|||||||
+2
-4
@@ -170,7 +170,7 @@ internal fun <T> Iterable<T>.fillToSize(
|
|||||||
transform: (List<T>) -> T,
|
transform: (List<T>) -> T,
|
||||||
): List<T> {
|
): List<T> {
|
||||||
val capacity = ceil(size.safeDiv(count())).roundToInt()
|
val capacity = ceil(size.safeDiv(count())).roundToInt()
|
||||||
return map { data -> List(capacity) { data } }.flatten().chunkToSize(size, transform)
|
return flatMap { data -> List(capacity) { data } }.chunkToSize(size, transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun <T> Iterable<T>.chunkToSize(
|
internal fun <T> Iterable<T>.chunkToSize(
|
||||||
@@ -207,6 +207,4 @@ internal fun Iterable<Float>.normalize(
|
|||||||
return values.map { scale * ((it - currentMin) / range) + min }
|
return values.map { scale * ((it - currentMin) / range) + min }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Int.safeDiv(value: Int): Float {
|
private fun Int.safeDiv(value: Int): Float = if (value == 0) 0F else this / value.toFloat()
|
||||||
return if (value == 0) return 0F else this / value.toFloat()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ fun LoadOrCreateNote(
|
|||||||
content: @Composable (Note?) -> Unit,
|
content: @Composable (Note?) -> Unit,
|
||||||
) {
|
) {
|
||||||
var note by
|
var note by
|
||||||
remember(event.id) { mutableStateOf<Note?>(accountViewModel.getNoteIfExists(event.id)) }
|
remember(event.id) { mutableStateOf(accountViewModel.getNoteIfExists(event.id)) }
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
LaunchedEffect(key1 = event.id) {
|
LaunchedEffect(key1 = event.id) {
|
||||||
|
|||||||
+1
-1
@@ -117,7 +117,7 @@ fun SensitivityWarning(
|
|||||||
@Composable
|
@Composable
|
||||||
fun ContentWarningNotePreview() {
|
fun ContentWarningNotePreview() {
|
||||||
ThemeComparisonColumn {
|
ThemeComparisonColumn {
|
||||||
ContentWarningNote({})
|
ContentWarningNote {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -49,7 +49,7 @@ class ChannelFeedContentState(
|
|||||||
val feedContent = _feedContent.asStateFlow()
|
val feedContent = _feedContent.asStateFlow()
|
||||||
|
|
||||||
// Simple counter that changes when it needs to invalidate everything
|
// Simple counter that changes when it needs to invalidate everything
|
||||||
private val _scrollToTop = MutableStateFlow<Int>(0)
|
private val _scrollToTop = MutableStateFlow(0)
|
||||||
val scrollToTop = _scrollToTop.asStateFlow()
|
val scrollToTop = _scrollToTop.asStateFlow()
|
||||||
var scrolltoTopPending = false
|
var scrolltoTopPending = false
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ class ChannelFeedContentState(
|
|||||||
viewModelScope.launch(Dispatchers.IO) { _scrollToTop.emit(_scrollToTop.value + 1) }
|
viewModelScope.launch(Dispatchers.IO) { _scrollToTop.emit(_scrollToTop.value + 1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sentToTop() {
|
fun sentToTop() {
|
||||||
scrolltoTopPending = false
|
scrolltoTopPending = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,10 +98,10 @@ class ChannelFeedContentState(
|
|||||||
if (notes.isEmpty()) {
|
if (notes.isEmpty()) {
|
||||||
_feedContent.tryEmit(ChannelFeedState.Empty)
|
_feedContent.tryEmit(ChannelFeedState.Empty)
|
||||||
} else if (currentState is ChannelFeedState.Loaded) {
|
} else if (currentState is ChannelFeedState.Loaded) {
|
||||||
currentState.feed.tryEmit(LoadedFeedState<Channel>(notes, localFilter.showHiddenKey()))
|
currentState.feed.tryEmit(LoadedFeedState(notes, localFilter.showHiddenKey()))
|
||||||
} else {
|
} else {
|
||||||
_feedContent.tryEmit(
|
_feedContent.tryEmit(
|
||||||
ChannelFeedState.Loaded(MutableStateFlow(LoadedFeedState<Channel>(notes, localFilter.showHiddenKey()))),
|
ChannelFeedState.Loaded(MutableStateFlow(LoadedFeedState(notes, localFilter.showHiddenKey()))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* 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.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.myapplication
|
package com.vitorpamplona.amethyst.ui.layouts
|
||||||
|
|
||||||
import androidx.compose.animation.core.AnimationSpec
|
import androidx.compose.animation.core.AnimationSpec
|
||||||
import androidx.compose.animation.core.AnimationState
|
import androidx.compose.animation.core.AnimationState
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* 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.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.myapplication
|
package com.vitorpamplona.amethyst.ui.layouts
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.BoxScope
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.myapplication.DisappearingBottomBar
|
|
||||||
import com.vitorpamplona.myapplication.DisappearingFloatingButton
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+1
-1
@@ -130,7 +130,7 @@ fun DisplayAccount(
|
|||||||
accountSessionManager: AccountSessionManager,
|
accountSessionManager: AccountSessionManager,
|
||||||
) {
|
) {
|
||||||
var baseUser by remember(acc) {
|
var baseUser by remember(acc) {
|
||||||
mutableStateOf<User?>(
|
mutableStateOf(
|
||||||
decodePublicKeyAsHexOrNull(acc.npub)?.let {
|
decodePublicKeyAsHexOrNull(acc.npub)?.let {
|
||||||
LocalCache.getUserIfExists(it)
|
LocalCache.getUserIfExists(it)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ class Nav(
|
|||||||
) {
|
) {
|
||||||
navigationScope.launch {
|
navigationScope.launch {
|
||||||
controller.navigate(route) {
|
controller.navigate(route) {
|
||||||
popUpTo<T>(klass) { inclusive = true }
|
popUpTo(klass) { inclusive = true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -323,7 +323,7 @@ suspend fun routeEditDraftTo(
|
|||||||
is ChatroomKeyable -> {
|
is ChatroomKeyable -> {
|
||||||
val room = draft.chatroomKey(account.userProfile().pubkeyHex)
|
val room = draft.chatroomKey(account.userProfile().pubkeyHex)
|
||||||
account.chatroomList.getOrCreatePrivateChatroom(room)
|
account.chatroomList.getOrCreatePrivateChatroom(room)
|
||||||
return Route.Room(room, draftId = note.idHex)
|
Route.Room(room, draftId = note.idHex)
|
||||||
}
|
}
|
||||||
|
|
||||||
is TextNoteEvent -> {
|
is TextNoteEvent -> {
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ fun HiddenNotePreview() {
|
|||||||
ThemeComparisonColumn(
|
ThemeComparisonColumn(
|
||||||
toPreview = {
|
toPreview = {
|
||||||
HiddenNote(
|
HiddenNote(
|
||||||
reports = persistentSetOf<Note>(),
|
reports = persistentSetOf(),
|
||||||
isHiddenAuthor = true,
|
isHiddenAuthor = true,
|
||||||
accountViewModel = mockAccountViewModel(),
|
accountViewModel = mockAccountViewModel(),
|
||||||
nav = EmptyNav(),
|
nav = EmptyNav(),
|
||||||
|
|||||||
+1
-1
@@ -167,7 +167,7 @@ fun ObserveAllStatusesToAvoidSwitchigAllTheTime(
|
|||||||
statuses: ImmutableList<AddressableNote>,
|
statuses: ImmutableList<AddressableNote>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
statuses.map {
|
statuses.forEach {
|
||||||
EventFinderFilterAssemblerSubscription(it, accountViewModel)
|
EventFinderFilterAssemblerSubscription(it, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
|||||||
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
|
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NoteCompose(
|
fun NoteCompose(
|
||||||
@@ -536,7 +537,7 @@ fun ClickableNote(
|
|||||||
|
|
||||||
nav.nav {
|
nav.nav {
|
||||||
if (redirectToNote.event is DraftWrapEvent) {
|
if (redirectToNote.event is DraftWrapEvent) {
|
||||||
with(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
routeEditDraftTo(redirectToNote, accountViewModel.account)
|
routeEditDraftTo(redirectToNote, accountViewModel.account)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -481,7 +481,7 @@ private fun ReactionDetailGallery(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val defaultBackgroundColor = MaterialTheme.colorScheme.background
|
val defaultBackgroundColor = MaterialTheme.colorScheme.background
|
||||||
val backgroundColor = remember { mutableStateOf<Color>(defaultBackgroundColor) }
|
val backgroundColor = remember { mutableStateOf(defaultBackgroundColor) }
|
||||||
|
|
||||||
val hasReactions by observeNoteReferences(baseNote, accountViewModel)
|
val hasReactions by observeNoteReferences(baseNote, accountViewModel)
|
||||||
|
|
||||||
@@ -757,7 +757,7 @@ private fun SlidingAnimationCount(
|
|||||||
if (accountViewModel.settings.isPerformanceMode()) {
|
if (accountViewModel.settings.isPerformanceMode()) {
|
||||||
TextCount(baseCount, textColor)
|
TextCount(baseCount, textColor)
|
||||||
} else {
|
} else {
|
||||||
AnimatedContent<Int>(
|
AnimatedContent(
|
||||||
targetState = baseCount,
|
targetState = baseCount,
|
||||||
transitionSpec = AnimatedContentTransitionScope<Int>::transitionSpec,
|
transitionSpec = AnimatedContentTransitionScope<Int>::transitionSpec,
|
||||||
label = "SlidingAnimationCount",
|
label = "SlidingAnimationCount",
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ fun ReplyInformationChannel(
|
|||||||
onUserTagClick: (User) -> Unit,
|
onUserTagClick: (User) -> Unit,
|
||||||
) {
|
) {
|
||||||
FlowRow {
|
FlowRow {
|
||||||
if (mentions != null && mentions.isNotEmpty()) {
|
if (!mentions.isNullOrEmpty()) {
|
||||||
if (replyTo != null && replyTo.isNotEmpty()) {
|
if (!replyTo.isNullOrEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
stringRes(id = R.string.replying_to),
|
stringRes(id = R.string.replying_to),
|
||||||
fontSize = 13.sp,
|
fontSize = 13.sp,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import kotlin.math.round
|
|||||||
private const val YEAR_DATE_FORMAT = "MMM dd, yyyy"
|
private const val YEAR_DATE_FORMAT = "MMM dd, yyyy"
|
||||||
private const val MONTH_DATE_FORMAT = "MMM dd"
|
private const val MONTH_DATE_FORMAT = "MMM dd"
|
||||||
|
|
||||||
var locale = Locale.getDefault()
|
var locale: Locale = Locale.getDefault()
|
||||||
var yearFormatter = SimpleDateFormat(YEAR_DATE_FORMAT, locale)
|
var yearFormatter = SimpleDateFormat(YEAR_DATE_FORMAT, locale)
|
||||||
var monthFormatter = SimpleDateFormat(MONTH_DATE_FORMAT, locale)
|
var monthFormatter = SimpleDateFormat(MONTH_DATE_FORMAT, locale)
|
||||||
|
|
||||||
|
|||||||
@@ -262,7 +262,7 @@ class PollNoteViewModel : ViewModel() {
|
|||||||
valueMinimum?.let { minimum ->
|
valueMinimum?.let { minimum ->
|
||||||
valueMaximum?.let { maximum ->
|
valueMaximum?.let { maximum ->
|
||||||
if (minimum != maximum) {
|
if (minimum != maximum) {
|
||||||
options.add(((minimum + maximum) / 2).toLong())
|
options.add(((minimum + maximum) / 2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -34,9 +34,9 @@ import kotlin.uuid.Uuid
|
|||||||
@Stable
|
@Stable
|
||||||
class DraftTagState {
|
class DraftTagState {
|
||||||
var current: String by mutableStateOf(newTag())
|
var current: String by mutableStateOf(newTag())
|
||||||
var usedDraftTags by mutableStateOf(setOf<String>(current))
|
var usedDraftTags by mutableStateOf(setOf(current))
|
||||||
|
|
||||||
private val _versions = MutableStateFlow<Int>(0)
|
private val _versions = MutableStateFlow(0)
|
||||||
|
|
||||||
@OptIn(FlowPreview::class)
|
@OptIn(FlowPreview::class)
|
||||||
val versions = _versions.debounce(1000)
|
val versions = _versions.debounce(1000)
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ class UserSuggestionState(
|
|||||||
val account: Account,
|
val account: Account,
|
||||||
val requireAtSymbol: Boolean = true,
|
val requireAtSymbol: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val invalidations = MutableStateFlow<Int>(0)
|
val invalidations = MutableStateFlow(0)
|
||||||
val currentWord = MutableStateFlow("")
|
val currentWord = MutableStateFlow("")
|
||||||
val searchDataSourceState = SearchQueryState(MutableStateFlow(""), account)
|
val searchDataSourceState = SearchQueryState(MutableStateFlow(""), account)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -51,7 +51,7 @@ fun DisplayZapSplits(
|
|||||||
remember(noteEvent) {
|
remember(noteEvent) {
|
||||||
val list = noteEvent.zapSplitSetup()
|
val list = noteEvent.zapSplitSetup()
|
||||||
if (list.isEmpty() && useAuthorIfEmpty) {
|
if (list.isEmpty() && useAuthorIfEmpty) {
|
||||||
listOf<ZapSplitSetup>(
|
listOf(
|
||||||
ZapSplitSetup(
|
ZapSplitSetup(
|
||||||
pubKeyHex = noteEvent.pubKey,
|
pubKeyHex = noteEvent.pubKey,
|
||||||
relay = null,
|
relay = null,
|
||||||
|
|||||||
@@ -116,13 +116,13 @@ private fun RenderPledgeAmount(
|
|||||||
) {
|
) {
|
||||||
val repliesState by observeNoteReplies(baseNote, accountViewModel)
|
val repliesState by observeNoteReplies(baseNote, accountViewModel)
|
||||||
var reward by remember {
|
var reward by remember {
|
||||||
mutableStateOf<String>(
|
mutableStateOf(
|
||||||
showAmount(baseReward.amount),
|
showAmount(baseReward.amount),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasPledge by remember {
|
var hasPledge by remember {
|
||||||
mutableStateOf<Boolean>(
|
mutableStateOf(
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -58,7 +58,7 @@ fun DisplayUncitedHashtags(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val unusedHashtags by
|
val unusedHashtags by
|
||||||
produceState(initialValue = emptyList<String>()) {
|
produceState(initialValue = emptyList()) {
|
||||||
val tagsInEvent = event.hashtags()
|
val tagsInEvent = event.hashtags()
|
||||||
if (tagsInEvent.isNotEmpty()) {
|
if (tagsInEvent.isNotEmpty()) {
|
||||||
val state = CachedRichTextParser.parseText(content, event.tags.toImmutableListOfLists(), callbackUri)
|
val state = CachedRichTextParser.parseText(content, event.tags.toImmutableListOfLists(), callbackUri)
|
||||||
@@ -66,15 +66,15 @@ fun DisplayUncitedHashtags(
|
|||||||
val tagsInContent =
|
val tagsInContent =
|
||||||
state
|
state
|
||||||
.paragraphs
|
.paragraphs
|
||||||
.map {
|
.flatMap { paragraphState ->
|
||||||
it.words.mapNotNull {
|
paragraphState.words.mapNotNull {
|
||||||
if (it is HashTagSegment) {
|
if (it is HashTagSegment) {
|
||||||
it.hashtag
|
it.hashtag
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.flatten()
|
}
|
||||||
|
|
||||||
val unusedHashtags =
|
val unusedHashtags =
|
||||||
tagsInEvent.filterNot { eventTag ->
|
tagsInEvent.filterNot { eventTag ->
|
||||||
|
|||||||
+2
-2
@@ -474,7 +474,7 @@ open class CommentPostViewModel :
|
|||||||
myMultiOrchestrator.upload(
|
myMultiOrchestrator.upload(
|
||||||
alt,
|
alt,
|
||||||
contentWarningReason,
|
contentWarningReason,
|
||||||
MediaCompressor.Companion.intToCompressorQuality(mediaQuality),
|
MediaCompressor.intToCompressorQuality(mediaQuality),
|
||||||
server,
|
server,
|
||||||
account,
|
account,
|
||||||
context,
|
context,
|
||||||
@@ -703,5 +703,5 @@ open class CommentPostViewModel :
|
|||||||
return location!!
|
return location!!
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun locationManager(): LocationState = Amethyst.Companion.instance.locationManager
|
override fun locationManager(): LocationState = Amethyst.instance.locationManager
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import com.vitorpamplona.quartz.nip30CustomEmoji.pack.EmojiPackEvent
|
|||||||
import com.vitorpamplona.quartz.nip30CustomEmoji.taggedEmojis
|
import com.vitorpamplona.quartz.nip30CustomEmoji.taggedEmojis
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun RenderEmojiPack(
|
fun RenderEmojiPack(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
actionable: Boolean,
|
actionable: Boolean,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
@@ -86,7 +86,7 @@ public fun RenderEmojiPack(
|
|||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
public fun RenderEmojiPack(
|
fun RenderEmojiPack(
|
||||||
noteEvent: EmojiPackEvent,
|
noteEvent: EmojiPackEvent,
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
actionable: Boolean,
|
actionable: Boolean,
|
||||||
|
|||||||
@@ -649,12 +649,12 @@ fun RenderPollGameResultsPreview() {
|
|||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
RenderPoll(
|
RenderPoll(
|
||||||
note,
|
note,
|
||||||
false,
|
makeItShort = false,
|
||||||
true,
|
canPreview = true,
|
||||||
2,
|
quotesLeft = 2,
|
||||||
remember { mutableStateOf(Color.Transparent) },
|
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||||
mockAccountViewModel(),
|
accountViewModel = mockAccountViewModel(),
|
||||||
EmptyNav(),
|
nav = EmptyNav(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -758,12 +758,12 @@ fun RenderPollColorResultsPreview() {
|
|||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
RenderPoll(
|
RenderPoll(
|
||||||
note,
|
note,
|
||||||
false,
|
makeItShort = false,
|
||||||
true,
|
canPreview = true,
|
||||||
2,
|
quotesLeft = 2,
|
||||||
remember { mutableStateOf(Color.Transparent) },
|
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||||
mockAccountViewModel(),
|
accountViewModel = mockAccountViewModel(),
|
||||||
EmptyNav(),
|
nav = EmptyNav(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.RenderUserAsClickableText
|
import com.vitorpamplona.amethyst.ui.components.RenderUserAsClickableText
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
@@ -102,7 +101,7 @@ fun DisplayUncitedUsers(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
@Suppress("ProduceStateDoesNotAssignValue")
|
@Suppress("ProduceStateDoesNotAssignValue")
|
||||||
val uncitedUsers by produceState(initialValue = emptyList<User>()) {
|
val uncitedUsers by produceState(initialValue = emptyList()) {
|
||||||
val users = event.groupKeySetWithoutOwner() - event.citedUsers()
|
val users = event.groupKeySetWithoutOwner() - event.citedUsers()
|
||||||
if (users.isNotEmpty()) {
|
if (users.isNotEmpty()) {
|
||||||
val newUsers = accountViewModel.loadUsersSync(users.toList())
|
val newUsers = accountViewModel.loadUsersSync(users.toList())
|
||||||
|
|||||||
@@ -139,12 +139,12 @@ fun TorrentCommentPreview() {
|
|||||||
toPreview = {
|
toPreview = {
|
||||||
RenderTorrentComment(
|
RenderTorrentComment(
|
||||||
comment,
|
comment,
|
||||||
false,
|
makeItShort = false,
|
||||||
true,
|
canPreview = true,
|
||||||
3,
|
quotesLeft = 3,
|
||||||
ReplyRenderType.FULL,
|
unPackReply = ReplyRenderType.FULL,
|
||||||
remember { mutableStateOf(Color.Transparent) },
|
backgroundColor = remember { mutableStateOf(Color.Transparent) },
|
||||||
EmptyState,
|
editState = EmptyState,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -118,7 +118,7 @@ class AccountSessionManager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun requestLoginUI() = _accountContent.update { AccountState.LoggedOff }
|
private fun requestLoginUI() = _accountContent.update { AccountState.LoggedOff }
|
||||||
|
|
||||||
suspend fun loginAndStartUI(
|
suspend fun loginAndStartUI(
|
||||||
key: String,
|
key: String,
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ class CompositionObserver(
|
|||||||
private val vmStore: StoreOwnerRegistry,
|
private val vmStore: StoreOwnerRegistry,
|
||||||
private val key: Any,
|
private val key: Any,
|
||||||
) : RememberObserver {
|
) : RememberObserver {
|
||||||
override fun onRemembered() {}
|
override fun onRemembered() {
|
||||||
|
// No action needed when remembered — registration happens at construction time.
|
||||||
|
}
|
||||||
|
|
||||||
override fun onForgotten() = vmStore.composableDetached(key)
|
override fun onForgotten() = vmStore.composableDetached(key)
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -357,10 +357,10 @@ class AccountViewModel(
|
|||||||
|
|
||||||
return if (isPostHidden || isDecryptedPostHidden) {
|
return if (isPostHidden || isDecryptedPostHidden) {
|
||||||
// Spam + Blocked Users + Hidden Words + Sensitive Content
|
// Spam + Blocked Users + Hidden Words + Sensitive Content
|
||||||
NoteComposeReportState(isPostHidden, false, false, isHiddenAuthor)
|
NoteComposeReportState(isPostHidden, isAcceptable = false, canPreview = false, isHiddenAuthor = isHiddenAuthor)
|
||||||
} else if (isFromLoggedIn || isFromLoggedInFollow) {
|
} else if (isFromLoggedIn || isFromLoggedInFollow) {
|
||||||
// No need to process if from trusted people
|
// No need to process if from trusted people
|
||||||
NoteComposeReportState(isPostHidden, true, true, isHiddenAuthor)
|
NoteComposeReportState(isPostHidden, isAcceptable = true, canPreview = true, isHiddenAuthor = isHiddenAuthor)
|
||||||
} else {
|
} else {
|
||||||
val newCanPreview = !note.hasAnyReports()
|
val newCanPreview = !note.hasAnyReports()
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ class AccountViewModel(
|
|||||||
|
|
||||||
if (newCanPreview && newIsAcceptable) {
|
if (newCanPreview && newIsAcceptable) {
|
||||||
// No need to process reports if nothing is wrong
|
// No need to process reports if nothing is wrong
|
||||||
NoteComposeReportState(isPostHidden, true, true, false)
|
NoteComposeReportState(isPostHidden, isAcceptable = true, canPreview = true, isHiddenAuthor = false)
|
||||||
} else {
|
} else {
|
||||||
NoteComposeReportState(
|
NoteComposeReportState(
|
||||||
isPostHidden,
|
isPostHidden,
|
||||||
@@ -511,7 +511,7 @@ class AccountViewModel(
|
|||||||
}.toMutableMap()
|
}.toMutableMap()
|
||||||
|
|
||||||
val results =
|
val results =
|
||||||
mapNotNullAsync<CombinedZap, DecryptedInfo>(
|
mapNotNullAsync(
|
||||||
zaps.filter { (it.request.event as? LnZapRequestEvent)?.isPrivateZap() == true },
|
zaps.filter { (it.request.event as? LnZapRequestEvent)?.isPrivateZap() == true },
|
||||||
) { next ->
|
) { next ->
|
||||||
val info = innerDecryptAmountMessage(next.request, next.response)
|
val info = innerDecryptAmountMessage(next.request, next.response)
|
||||||
@@ -1609,7 +1609,7 @@ class AccountViewModel(
|
|||||||
}.stateIn(
|
}.stateIn(
|
||||||
viewModelScope,
|
viewModelScope,
|
||||||
SharingStarted.WhileSubscribed(5000),
|
SharingStarted.WhileSubscribed(5000),
|
||||||
emptySet<HexKey>(),
|
emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
val draftNoteCache = CachedDraftNotes(this)
|
val draftNoteCache = CachedDraftNotes(this)
|
||||||
|
|||||||
+1
-1
@@ -173,7 +173,7 @@ fun ChatBubbleLayout(
|
|||||||
private fun BubblePreview() {
|
private fun BubblePreview() {
|
||||||
val bgColor =
|
val bgColor =
|
||||||
remember {
|
remember {
|
||||||
mutableStateOf<Color>(Color.Transparent)
|
mutableStateOf(Color.Transparent)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
+2
-2
@@ -222,8 +222,8 @@ fun RenderRelayLinePreview() {
|
|||||||
"wss://nos.lol",
|
"wss://nos.lol",
|
||||||
"http://icon.com/icon.ico",
|
"http://icon.com/icon.ico",
|
||||||
Modifier,
|
Modifier,
|
||||||
true,
|
showPicture = true,
|
||||||
true,
|
loadRobohash = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ fun filterNip04DMs(
|
|||||||
account: Account?,
|
account: Account?,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? {
|
): List<RelayBasedFilter>? {
|
||||||
if (group == null || group.isEmpty() || account == null) return null
|
if (group.isNullOrEmpty() || account == null) return null
|
||||||
|
|
||||||
val userOutboxRelays = account.homeRelays.flow.value
|
val userOutboxRelays = account.homeRelays.flow.value
|
||||||
val userInboxRelays = account.dmRelays.flow.value
|
val userInboxRelays = account.dmRelays.flow.value
|
||||||
|
|||||||
+2
-2
@@ -73,14 +73,14 @@ fun NewChatroomSubjectDialog(
|
|||||||
Surface {
|
Surface {
|
||||||
val groupName =
|
val groupName =
|
||||||
remember {
|
remember {
|
||||||
mutableStateOf<String>(
|
mutableStateOf(
|
||||||
accountViewModel.account.chatroomList.rooms
|
accountViewModel.account.chatroomList.rooms
|
||||||
.get(room)
|
.get(room)
|
||||||
?.subject
|
?.subject
|
||||||
?.value ?: "",
|
?.value ?: "",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val message = remember { mutableStateOf<String>("") }
|
val message = remember { mutableStateOf("") }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
+1
-1
@@ -596,7 +596,7 @@ class ChatNewMessageViewModel :
|
|||||||
toUsersTagger.run()
|
toUsersTagger.run()
|
||||||
|
|
||||||
val users = toUsersTagger.pTags?.mapTo(mutableSetOf()) { it.pubkeyHex }
|
val users = toUsersTagger.pTags?.mapTo(mutableSetOf()) { it.pubkeyHex }
|
||||||
if (users == null || users.isEmpty()) {
|
if (users.isNullOrEmpty()) {
|
||||||
room = null
|
room = null
|
||||||
updateNIP17StatusFromRoom()
|
updateNIP17StatusFromRoom()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -29,7 +29,7 @@ import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
|||||||
fun filterMessagesToPublicChat(
|
fun filterMessagesToPublicChat(
|
||||||
channel: PublicChatChannel,
|
channel: PublicChatChannel,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? =
|
): List<RelayBasedFilter> =
|
||||||
channel.relays().toSet().map {
|
channel.relays().toSet().map {
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
relay = it,
|
relay = it,
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@ fun filterMyMessagesToLiveActivities(
|
|||||||
channel: LiveActivitiesChannel,
|
channel: LiveActivitiesChannel,
|
||||||
pubKey: HexKey,
|
pubKey: HexKey,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? =
|
): List<RelayBasedFilter> =
|
||||||
channel.relays().toSet().map {
|
channel.relays().toSet().map {
|
||||||
RelayBasedFilter(
|
RelayBasedFilter(
|
||||||
relay = it,
|
relay = it,
|
||||||
|
|||||||
+1
-1
@@ -129,7 +129,7 @@ class ChannelMetadataViewModel : ViewModel() {
|
|||||||
|
|
||||||
val template =
|
val template =
|
||||||
if (event != null) {
|
if (event != null) {
|
||||||
val hint = EventHintBundle<ChannelCreateEvent>(event, channel.relays().firstOrNull())
|
val hint = EventHintBundle(event, channel.relays().firstOrNull())
|
||||||
|
|
||||||
ChannelMetadataEvent.build(
|
ChannelMetadataEvent.build(
|
||||||
channelName.value.text,
|
channelName.value.text,
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ class FollowingEphemeralChatSubAssembler(
|
|||||||
override fun updateFilter(
|
override fun updateFilter(
|
||||||
key: ChatroomListState,
|
key: ChatroomListState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? =
|
): List<RelayBasedFilter> =
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
filterFollowingEphemeralChats(key.account.ephemeralChatList.liveEphemeralChatList.value, since),
|
filterFollowingEphemeralChats(key.account.ephemeralChatList.liveEphemeralChatList.value, since),
|
||||||
).flatten()
|
).flatten()
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ class FollowingPublicChatSubAssembler(
|
|||||||
override fun updateFilter(
|
override fun updateFilter(
|
||||||
key: ChatroomListState,
|
key: ChatroomListState,
|
||||||
since: SincePerRelayMap?,
|
since: SincePerRelayMap?,
|
||||||
): List<RelayBasedFilter>? =
|
): List<RelayBasedFilter> =
|
||||||
listOfNotNull(
|
listOfNotNull(
|
||||||
filterLastMessageFollowingPublicChats(key.account.publicChatList.flowSet.value, since),
|
filterLastMessageFollowingPublicChats(key.account.publicChatList.flowSet.value, since),
|
||||||
filterFollowingPublicChatsCreationEvent(key.account.publicChatList.flowSet.value, since),
|
filterFollowingPublicChatsCreationEvent(key.account.publicChatList.flowSet.value, since),
|
||||||
|
|||||||
-1
@@ -29,7 +29,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
|||||||
-1
@@ -42,7 +42,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
|||||||
+1
-2
@@ -117,8 +117,7 @@ open class DiscoverLiveFeedFilter(
|
|||||||
|
|
||||||
fun convertStatusToOrder(event: LiveActivitiesEvent?): Int {
|
fun convertStatusToOrder(event: LiveActivitiesEvent?): Int {
|
||||||
if (event == null) return 0
|
if (event == null) return 0
|
||||||
val url = event.streaming()
|
val url = event.streaming() ?: return 0
|
||||||
if (url == null) return 0
|
|
||||||
return when (event.status()) {
|
return when (event.status()) {
|
||||||
StatusTag.STATUS.LIVE -> {
|
StatusTag.STATUS.LIVE -> {
|
||||||
if (OnlineChecker.isCachedAndOffline(url)) {
|
if (OnlineChecker.isCachedAndOffline(url)) {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user