Merge remote-tracking branch 'origin/kmp-completeness' into kmp-completeness
This commit is contained in:
@@ -348,6 +348,9 @@ dependencies {
|
|||||||
// Image compression lib
|
// Image compression lib
|
||||||
implementation libs.zelory.image.compressor
|
implementation libs.zelory.image.compressor
|
||||||
|
|
||||||
|
// Voice anonymization DSP
|
||||||
|
implementation libs.tarsosdsp
|
||||||
|
|
||||||
// Cbor for cashuB format
|
// Cbor for cashuB format
|
||||||
implementation libs.kotlinx.serialization.cbor
|
implementation libs.kotlinx.serialization.cbor
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -22,13 +22,13 @@ package com.vitorpamplona.amethyst
|
|||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.vitorpamplona.amethyst.commons.viewmodels.thread.ThreadFeedFilter
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket
|
import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal.ThreadFeedFilter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.verify
|
import com.vitorpamplona.quartz.nip01Core.crypto.verify
|
||||||
@@ -174,7 +174,7 @@ class ThreadDualAxisChartAssemblerTest {
|
|||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val filter = ThreadFeedFilter(account, naddr.toTag())
|
val filter = ThreadFeedFilter(account, naddr.toTag(), LocalCache)
|
||||||
val calculatedFeed = filter.feed()
|
val calculatedFeed = filter.feed()
|
||||||
|
|
||||||
val expectedOrder =
|
val expectedOrder =
|
||||||
|
|||||||
@@ -26,8 +26,11 @@ import android.content.pm.ApplicationInfo
|
|||||||
import android.os.Debug
|
import android.os.Debug
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizedUrls
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizedUrls
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||||
|
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||||
import kotlin.time.DurationUnit
|
import kotlin.time.DurationUnit
|
||||||
import kotlin.time.measureTimedValue
|
import kotlin.time.measureTimedValue
|
||||||
|
|
||||||
@@ -228,3 +231,12 @@ inline fun debug(
|
|||||||
Log.d(tag, debugMessage())
|
Log.d(tag, debugMessage())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Event.countMemory(): Int =
|
||||||
|
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
|
||||||
|
12 + // createdAt + kind
|
||||||
|
id.bytesUsedInMemory() +
|
||||||
|
pubKey.bytesUsedInMemory() +
|
||||||
|
tags.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } +
|
||||||
|
content.bytesUsedInMemory() +
|
||||||
|
sig.bytesUsedInMemory()
|
||||||
|
|||||||
@@ -24,15 +24,21 @@ import androidx.compose.runtime.Stable
|
|||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.LocalPreferences
|
import com.vitorpamplona.amethyst.LocalPreferences
|
||||||
import com.vitorpamplona.amethyst.commons.model.IAccount
|
import com.vitorpamplona.amethyst.commons.model.IAccount
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatListDecryptionCache
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatListState
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip18Reposts.RepostAction
|
import com.vitorpamplona.amethyst.commons.model.nip18Reposts.RepostAction
|
||||||
import com.vitorpamplona.amethyst.commons.model.nip25Reactions.ReactionAction
|
import com.vitorpamplona.amethyst.commons.model.nip25Reactions.ReactionAction
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatListDecryptionCache
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatListState
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip38UserStatuses.UserStatusAction
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip56Reports.ReportAction
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||||
import com.vitorpamplona.amethyst.logTime
|
import com.vitorpamplona.amethyst.logTime
|
||||||
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListDecryptionCache
|
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListDecryptionCache
|
||||||
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState
|
import com.vitorpamplona.amethyst.model.edits.PrivateStorageRelayListState
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatListDecryptionCache
|
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatListState
|
|
||||||
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
|
import com.vitorpamplona.amethyst.model.localRelays.LocalRelayListState
|
||||||
import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountHomeRelayState
|
import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountHomeRelayState
|
||||||
import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountOutboxRelayState
|
import com.vitorpamplona.amethyst.model.nip01UserMetadata.AccountOutboxRelayState
|
||||||
@@ -46,11 +52,6 @@ import com.vitorpamplona.amethyst.model.nip02FollowLists.Kind3FollowListState
|
|||||||
import com.vitorpamplona.amethyst.model.nip03Timestamp.OtsState
|
import com.vitorpamplona.amethyst.model.nip03Timestamp.OtsState
|
||||||
import com.vitorpamplona.amethyst.model.nip17Dms.DmInboxRelayState
|
import com.vitorpamplona.amethyst.model.nip17Dms.DmInboxRelayState
|
||||||
import com.vitorpamplona.amethyst.model.nip17Dms.DmRelayListState
|
import com.vitorpamplona.amethyst.model.nip17Dms.DmRelayListState
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatListDecryptionCache
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatListState
|
|
||||||
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
|
|
||||||
import com.vitorpamplona.amethyst.model.nip38UserStatuses.UserStatusAction
|
|
||||||
import com.vitorpamplona.amethyst.model.nip47WalletConnect.NwcSignerState
|
import com.vitorpamplona.amethyst.model.nip47WalletConnect.NwcSignerState
|
||||||
import com.vitorpamplona.amethyst.model.nip51Lists.BookmarkListState
|
import com.vitorpamplona.amethyst.model.nip51Lists.BookmarkListState
|
||||||
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
||||||
@@ -77,7 +78,6 @@ import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListD
|
|||||||
import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListState
|
import com.vitorpamplona.amethyst.model.nip51Lists.searchRelays.SearchRelayListState
|
||||||
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListDecryptionCache
|
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListDecryptionCache
|
||||||
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListState
|
import com.vitorpamplona.amethyst.model.nip51Lists.trustedRelays.TrustedRelayListState
|
||||||
import com.vitorpamplona.amethyst.model.nip56Reports.ReportAction
|
|
||||||
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
|
import com.vitorpamplona.amethyst.model.nip65RelayList.Nip65RelayListState
|
||||||
import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListDecryptionCache
|
import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListDecryptionCache
|
||||||
import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState
|
import com.vitorpamplona.amethyst.model.nip72Communities.CommunityListState
|
||||||
@@ -571,7 +571,8 @@ class Account(
|
|||||||
suspend fun report(
|
suspend fun report(
|
||||||
user: User,
|
user: User,
|
||||||
type: ReportType,
|
type: ReportType,
|
||||||
) = sendMyPublicAndPrivateOutbox(ReportAction.report(user, type, userProfile(), signer))
|
content: String = "",
|
||||||
|
) = sendMyPublicAndPrivateOutbox(ReportAction.report(user, type, content, userProfile(), signer))
|
||||||
|
|
||||||
suspend fun delete(note: Note) = delete(listOf(note))
|
suspend fun delete(note: Note) = delete(listOf(note))
|
||||||
|
|
||||||
@@ -1658,11 +1659,11 @@ class Account(
|
|||||||
|
|
||||||
fun isAllHidden(users: Set<HexKey>): Boolean = users.all { isHidden(it) }
|
fun isAllHidden(users: Set<HexKey>): Boolean = users.all { isHidden(it) }
|
||||||
|
|
||||||
fun isHidden(user: User) = isHidden(user.pubkeyHex)
|
override fun isHidden(user: User) = isHidden(user.pubkeyHex)
|
||||||
|
|
||||||
fun isHidden(userHex: String): Boolean = hiddenUsers.flow.value.isUserHidden(userHex)
|
fun isHidden(userHex: String): Boolean = hiddenUsers.flow.value.isUserHidden(userHex)
|
||||||
|
|
||||||
fun followingKeySet(): Set<HexKey> = kind3FollowList.flow.value.authors
|
override fun followingKeySet(): Set<HexKey> = kind3FollowList.flow.value.authors
|
||||||
|
|
||||||
fun isAcceptable(user: User): Boolean {
|
fun isAcceptable(user: User): Boolean {
|
||||||
if (userProfile().pubkeyHex == user.pubkeyHex) {
|
if (userProfile().pubkeyHex == user.pubkeyHex) {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatRepository
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatListRepository
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.DEFAULT_MEDIA_SERVERS
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerName
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedDefinition
|
import com.vitorpamplona.amethyst.ui.screen.FeedDefinition
|
||||||
@@ -143,7 +145,8 @@ class AccountSettings(
|
|||||||
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
||||||
var hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf<String>()),
|
var hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf<String>()),
|
||||||
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow<Map<HexKey, String>>(mapOf()),
|
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow<Map<HexKey, String>>(mapOf()),
|
||||||
) {
|
) : EphemeralChatRepository,
|
||||||
|
PublicChatListRepository {
|
||||||
val saveable = MutableStateFlow(AccountSettingsUpdater(null))
|
val saveable = MutableStateFlow(AccountSettingsUpdater(null))
|
||||||
val syncedSettings: AccountSyncedSettings = AccountSyncedSettings(AccountSyncedSettingsInternal())
|
val syncedSettings: AccountSyncedSettings = AccountSyncedSettings(AccountSyncedSettingsInternal())
|
||||||
|
|
||||||
@@ -389,7 +392,9 @@ class AccountSettings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateChannelListTo(newChannelList: ChannelListEvent?) {
|
override fun channelList() = backupChannelList
|
||||||
|
|
||||||
|
override fun updateChannelListTo(newChannelList: ChannelListEvent?) {
|
||||||
if (newChannelList == null || newChannelList.tags.isEmpty()) return
|
if (newChannelList == null || newChannelList.tags.isEmpty()) return
|
||||||
|
|
||||||
// Events might be different objects, we have to compare their ids.
|
// Events might be different objects, we have to compare their ids.
|
||||||
@@ -429,7 +434,9 @@ class AccountSettings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateEphemeralChatListTo(newEphemeralChatList: EphemeralChatListEvent?) {
|
override fun ephemeralChatList() = backupEphemeralChatList
|
||||||
|
|
||||||
|
override fun updateEphemeralChatListTo(newEphemeralChatList: EphemeralChatListEvent?) {
|
||||||
if (newEphemeralChatList == null || newEphemeralChatList.tags.isEmpty()) return
|
if (newEphemeralChatList == null || newEphemeralChatList.tags.isEmpty()) return
|
||||||
|
|
||||||
// Events might be different objects, we have to compare their ids.
|
// Events might be different objects, we have to compare their ids.
|
||||||
|
|||||||
@@ -106,12 +106,11 @@ class AntiSpamFilter {
|
|||||||
recentAddressables.put(hash, address)
|
recentAddressables.put(hash, address)
|
||||||
} else {
|
} else {
|
||||||
// normal event
|
// normal event
|
||||||
|
val existingEvent = recentEventIds[hash]
|
||||||
if (
|
if (
|
||||||
(recentEventIds[hash] != null && recentEventIds[hash] != event.id) ||
|
(existingEvent != null && existingEvent != event.id) ||
|
||||||
(spamMessages[hash] != null && !spamMessages[hash].duplicatedEventIds.contains(event.id))
|
(spamMessages[hash] != null && !spamMessages[hash].duplicatedEventIds.contains(event.id))
|
||||||
) {
|
) {
|
||||||
val existingEvent = recentEventIds[hash]
|
|
||||||
|
|
||||||
val link1 = njumpLink(NEvent.create(existingEvent, null, null, relay))
|
val link1 = njumpLink(NEvent.create(existingEvent, null, null, relay))
|
||||||
val link2 = njumpLink(NEvent.create(event.id, null, null, relay))
|
val link2 = njumpLink(NEvent.create(event.id, null, null, relay))
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,16 @@ package com.vitorpamplona.amethyst.model
|
|||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.cache.ICacheProvider
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.privateChats.ChatroomList
|
||||||
import com.vitorpamplona.amethyst.isDebug
|
import com.vitorpamplona.amethyst.isDebug
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
import com.vitorpamplona.amethyst.model.nip51Lists.HiddenUsersState
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.observables.LatestByKindAndAuthor
|
import com.vitorpamplona.amethyst.model.observables.LatestByKindAndAuthor
|
||||||
import com.vitorpamplona.amethyst.model.observables.LatestByKindWithETag
|
import com.vitorpamplona.amethyst.model.observables.LatestByKindWithETag
|
||||||
import com.vitorpamplona.amethyst.model.privateChats.ChatroomList
|
|
||||||
import com.vitorpamplona.amethyst.service.BundledInsert
|
import com.vitorpamplona.amethyst.service.BundledInsert
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.note.dateFormatter
|
import com.vitorpamplona.amethyst.ui.note.dateFormatter
|
||||||
@@ -212,7 +214,7 @@ interface ILocalCache {
|
|||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
object LocalCache : ILocalCache {
|
object LocalCache : ILocalCache, ICacheProvider {
|
||||||
val antiSpam = AntiSpamFilter()
|
val antiSpam = AntiSpamFilter()
|
||||||
|
|
||||||
val users = LargeSoftCache<HexKey, User>()
|
val users = LargeSoftCache<HexKey, User>()
|
||||||
@@ -317,16 +319,24 @@ object LocalCache : ILocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserIfExists(key: String): User? {
|
override fun getUserIfExists(key: String): User? {
|
||||||
if (key.isEmpty()) return null
|
if (key.isEmpty()) return null
|
||||||
return users.get(key)
|
return users.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun countUsers(predicate: (String, User) -> Boolean): Int {
|
||||||
|
var count = 0
|
||||||
|
users.forEach { key, user ->
|
||||||
|
if (predicate(key, user)) count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
fun getAddressableNoteIfExists(key: String): AddressableNote? = Address.parse(key)?.let { addressables.get(it) }
|
fun getAddressableNoteIfExists(key: String): AddressableNote? = Address.parse(key)?.let { addressables.get(it) }
|
||||||
|
|
||||||
fun getAddressableNoteIfExists(address: Address): AddressableNote? = addressables.get(address)
|
fun getAddressableNoteIfExists(address: Address): AddressableNote? = addressables.get(address)
|
||||||
|
|
||||||
fun getNoteIfExists(key: String): Note? = if (key.length == 64) notes.get(key) else Address.parse(key)?.let { addressables.get(it) }
|
override fun getNoteIfExists(key: String): Note? = if (key.length == 64) notes.get(key) else Address.parse(key)?.let { addressables.get(it) }
|
||||||
|
|
||||||
fun getNoteIfExists(key: ETag): Note? = notes.get(key.eventId)
|
fun getNoteIfExists(key: ETag): Note? = notes.get(key.eventId)
|
||||||
|
|
||||||
@@ -357,7 +367,7 @@ object LocalCache : ILocalCache {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkGetOrCreateNote(key: String): Note? {
|
override fun checkGetOrCreateNote(key: String): Note? {
|
||||||
if (ATag.isATag(key)) {
|
if (ATag.isATag(key)) {
|
||||||
return checkGetOrCreateAddressableNote(key)
|
return checkGetOrCreateAddressableNote(key)
|
||||||
}
|
}
|
||||||
@@ -382,6 +392,19 @@ object LocalCache : ILocalCache {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getEventStream(): com.vitorpamplona.amethyst.commons.model.cache.ICacheEventStream =
|
||||||
|
object : com.vitorpamplona.amethyst.commons.model.cache.ICacheEventStream {
|
||||||
|
override val newEventBundles = live.newEventBundles
|
||||||
|
override val deletedEventBundles = live.deletedEventBundles
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasBeenDeleted(event: Any): Boolean =
|
||||||
|
if (event is Event) {
|
||||||
|
deletionIndex.hasBeenDeleted(event)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
fun getOrAddAliasNote(
|
fun getOrAddAliasNote(
|
||||||
idHex: String,
|
idHex: String,
|
||||||
note: Note,
|
note: Note,
|
||||||
@@ -436,7 +459,7 @@ object LocalCache : ILocalCache {
|
|||||||
|
|
||||||
fun getOrCreateAddressableNoteInternal(key: Address): AddressableNote = addressables.getOrCreate(key) { AddressableNote(key) }
|
fun getOrCreateAddressableNoteInternal(key: Address): AddressableNote = addressables.getOrCreate(key) { AddressableNote(key) }
|
||||||
|
|
||||||
fun getOrCreateAddressableNote(key: Address): AddressableNote {
|
override fun getOrCreateAddressableNote(key: Address): AddressableNote {
|
||||||
val note = getOrCreateAddressableNoteInternal(key)
|
val note = getOrCreateAddressableNoteInternal(key)
|
||||||
// Loads the user outside a Syncronized block to avoid blocking
|
// Loads the user outside a Syncronized block to avoid blocking
|
||||||
if (note.author == null) {
|
if (note.author == null) {
|
||||||
@@ -497,9 +520,13 @@ object LocalCache : ILocalCache {
|
|||||||
|
|
||||||
// avoids processing empty contact lists.
|
// avoids processing empty contact lists.
|
||||||
if (event.createdAt > (user.latestContactList?.createdAt ?: 0) && !event.tags.isEmpty() && (wasVerified || justVerify(event))) {
|
if (event.createdAt > (user.latestContactList?.createdAt ?: 0) && !event.tags.isEmpty() && (wasVerified || justVerify(event))) {
|
||||||
user.updateContactList(event)
|
val needsToUpdateFollowers = user.updateContactList(event)
|
||||||
// Log.d("CL", "Consumed contact list ${user.toNostrUri()} ${event.relays()?.size}")
|
// Log.d("CL", "Consumed contact list ${user.toNostrUri()} ${event.relays()?.size}")
|
||||||
|
|
||||||
|
needsToUpdateFollowers.forEach {
|
||||||
|
getUserIfExists(it)?.flowSet?.followers?.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
updateObservables(event)
|
updateObservables(event)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -1371,7 +1398,7 @@ object LocalCache : ILocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAnyChannel(note: Note): Channel? = note.event?.let { getAnyChannel(it) }
|
override fun getAnyChannel(note: Note): Channel? = note.event?.let { getAnyChannel(it) }
|
||||||
|
|
||||||
fun getAnyChannel(noteEvent: Event): Channel? =
|
fun getAnyChannel(noteEvent: Event): Channel? =
|
||||||
when (noteEvent) {
|
when (noteEvent) {
|
||||||
@@ -2690,7 +2717,7 @@ object LocalCache : ILocalCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun justConsumeMyOwnEvent(event: Event) = justConsumeAndUpdateIndexes(event, null, true)
|
override fun justConsumeMyOwnEvent(event: Event) = justConsumeAndUpdateIndexes(event, null, true)
|
||||||
|
|
||||||
fun justConsume(
|
fun justConsume(
|
||||||
event: Event,
|
event: Event,
|
||||||
|
|||||||
@@ -24,6 +24,4 @@ package com.vitorpamplona.amethyst.model
|
|||||||
typealias Note = com.vitorpamplona.amethyst.commons.model.Note
|
typealias Note = com.vitorpamplona.amethyst.commons.model.Note
|
||||||
typealias NotesGatherer = com.vitorpamplona.amethyst.commons.model.NotesGatherer
|
typealias NotesGatherer = com.vitorpamplona.amethyst.commons.model.NotesGatherer
|
||||||
typealias AddressableNote = com.vitorpamplona.amethyst.commons.model.AddressableNote
|
typealias AddressableNote = com.vitorpamplona.amethyst.commons.model.AddressableNote
|
||||||
typealias NoteFlowSet = com.vitorpamplona.amethyst.commons.model.NoteFlowSet
|
|
||||||
typealias NoteBundledRefresherFlow = com.vitorpamplona.amethyst.commons.model.NoteBundledRefresherFlow
|
|
||||||
typealias NoteState = com.vitorpamplona.amethyst.commons.model.NoteState
|
typealias NoteState = com.vitorpamplona.amethyst.commons.model.NoteState
|
||||||
|
|||||||
@@ -21,9 +21,6 @@
|
|||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
// Re-export from commons for backwards compatibility
|
// Re-export from commons for backwards compatibility
|
||||||
typealias UserDependencies = com.vitorpamplona.amethyst.commons.model.UserDependencies
|
|
||||||
typealias User = com.vitorpamplona.amethyst.commons.model.User
|
typealias User = com.vitorpamplona.amethyst.commons.model.User
|
||||||
typealias UserFlowSet = com.vitorpamplona.amethyst.commons.model.UserFlowSet
|
|
||||||
typealias RelayInfo = com.vitorpamplona.amethyst.commons.model.RelayInfo
|
typealias RelayInfo = com.vitorpamplona.amethyst.commons.model.RelayInfo
|
||||||
typealias UserBundledRefresherFlow = com.vitorpamplona.amethyst.commons.model.UserBundledRefresherFlow
|
|
||||||
typealias UserState = com.vitorpamplona.amethyst.commons.model.UserState
|
typealias UserState = com.vitorpamplona.amethyst.commons.model.UserState
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service
|
||||||
|
|
||||||
|
// Re-export from commons for backwards compatibility
|
||||||
|
typealias BundledUpdate = com.vitorpamplona.amethyst.commons.service.BundledUpdate
|
||||||
|
typealias BasicBundledUpdate = com.vitorpamplona.amethyst.commons.service.BasicBundledUpdate
|
||||||
|
typealias BundledInsert<T> = com.vitorpamplona.amethyst.commons.service.BundledInsert<T>
|
||||||
|
typealias BasicBundledInsert<T> = com.vitorpamplona.amethyst.commons.service.BasicBundledInsert<T>
|
||||||
@@ -30,8 +30,8 @@ import coil3.fetch.Fetcher
|
|||||||
import coil3.fetch.ImageFetchResult
|
import coil3.fetch.ImageFetchResult
|
||||||
import coil3.key.Keyer
|
import coil3.key.Keyer
|
||||||
import coil3.request.Options
|
import coil3.request.Options
|
||||||
import com.vitorpamplona.amethyst.commons.base64Image.Base64Image
|
|
||||||
import com.vitorpamplona.amethyst.commons.base64Image.toBitmap
|
import com.vitorpamplona.amethyst.commons.base64Image.toBitmap
|
||||||
|
import com.vitorpamplona.amethyst.commons.richtext.Base64Image
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.mixChatsLive.ChannelMetadataAndLiveActivityWatcherSubAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.mixChatsLive.ChannelMetadataAndLiveActivityWatcherSubAssembler
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.ChannelLoaderSubAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.ChannelLoaderSubAssembler
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -24,11 +24,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.ChannelState
|
import com.vitorpamplona.amethyst.commons.model.ChannelState
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEvent
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|||||||
+2
-2
@@ -20,8 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.mixChatsLive
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.mixChatsLive
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.SingleSubEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.filterChannelMetadataUpdatesById
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats.filterChannelMetadataUpdatesById
|
||||||
|
|||||||
+2
-2
@@ -20,8 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip28PublicChats
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip53LiveActivities
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip53LiveActivities
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
|||||||
+2
-2
@@ -20,8 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip53LiveActivities
|
package com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.nip53LiveActivities
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.ChannelFinderQueryState
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
|
|||||||
+6
-3
@@ -25,17 +25,18 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.NoteState
|
import com.vitorpamplona.amethyst.model.NoteState
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.UserState
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
import com.vitorpamplona.quartz.nip01Core.metadata.UserMetadata
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||||
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
|
import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent
|
||||||
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
|
import com.vitorpamplona.quartz.nip51Lists.hashtagList.HashtagListEvent
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
@@ -392,7 +393,9 @@ fun observeUserFollowerCount(
|
|||||||
.followers.stateFlow
|
.followers.stateFlow
|
||||||
.sample(200)
|
.sample(200)
|
||||||
.mapLatest { userState ->
|
.mapLatest { userState ->
|
||||||
userState.user.transientFollowerCount()
|
LocalCache.countUsers { _, user ->
|
||||||
|
user.latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
||||||
|
}
|
||||||
}.distinctUntilChanged()
|
}.distinctUntilChanged()
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
|
|||||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
|
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens to NostrClient's onNotify messages from the relay
|
* Listens to NostrClient's onNotify messages from the relay
|
||||||
@@ -47,7 +48,7 @@ class RelaySpeedLogger(
|
|||||||
msg: Message,
|
msg: Message,
|
||||||
) {
|
) {
|
||||||
if (msg is EventMessage) {
|
if (msg is EventMessage) {
|
||||||
current.increment(msg.event.kind, msg.subId, relay.url, msg.event.countMemory())
|
current.increment(msg.event.kind, msg.subId, relay.url, msgStr.bytesUsedInMemory())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.actions.uploads
|
||||||
|
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class VoiceAnonymizationController(
|
||||||
|
private val scope: CoroutineScope,
|
||||||
|
private val logTag: String,
|
||||||
|
private val onError: (Throwable) -> Unit,
|
||||||
|
) {
|
||||||
|
var selectedPreset: VoicePreset by mutableStateOf(VoicePreset.NONE)
|
||||||
|
private set
|
||||||
|
var processingPreset: VoicePreset? by mutableStateOf(null)
|
||||||
|
private set
|
||||||
|
var distortedFiles: Map<VoicePreset, AnonymizedResult> by mutableStateOf(emptyMap())
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var processingJob: Job? = null
|
||||||
|
|
||||||
|
fun activeFile(originalFile: File?): File? =
|
||||||
|
if (selectedPreset == VoicePreset.NONE) {
|
||||||
|
originalFile
|
||||||
|
} else {
|
||||||
|
distortedFiles[selectedPreset]?.file
|
||||||
|
}
|
||||||
|
|
||||||
|
fun activeWaveform(originalWaveform: List<Float>?): List<Float>? =
|
||||||
|
if (selectedPreset == VoicePreset.NONE) {
|
||||||
|
originalWaveform
|
||||||
|
} else {
|
||||||
|
distortedFiles[selectedPreset]?.waveform
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectPreset(
|
||||||
|
preset: VoicePreset,
|
||||||
|
originalFile: File?,
|
||||||
|
) {
|
||||||
|
if (processingPreset != null || preset == selectedPreset) return
|
||||||
|
|
||||||
|
if (preset == VoicePreset.NONE) {
|
||||||
|
selectedPreset = preset
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distortedFiles.containsKey(preset)) {
|
||||||
|
selectedPreset = preset
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = originalFile ?: return
|
||||||
|
|
||||||
|
processingJob?.cancel()
|
||||||
|
processingJob =
|
||||||
|
scope.launch {
|
||||||
|
processingPreset = preset
|
||||||
|
try {
|
||||||
|
val anonymizer = VoiceAnonymizer()
|
||||||
|
val result = anonymizer.anonymize(file, preset)
|
||||||
|
|
||||||
|
result
|
||||||
|
.onSuccess { anonymizedResult ->
|
||||||
|
distortedFiles = distortedFiles + (preset to anonymizedResult)
|
||||||
|
selectedPreset = preset
|
||||||
|
}.onFailure { error ->
|
||||||
|
Log.w(logTag, "Failed to anonymize voice", error)
|
||||||
|
onError(error)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
processingPreset = null
|
||||||
|
processingJob = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
cancelProcessing()
|
||||||
|
deleteDistortedFiles()
|
||||||
|
selectedPreset = VoicePreset.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteDistortedFiles() {
|
||||||
|
distortedFiles.values.forEach { result ->
|
||||||
|
try {
|
||||||
|
if (result.file.exists()) {
|
||||||
|
result.file.delete()
|
||||||
|
Log.d(logTag, "Deleted distorted file: ${result.file.absolutePath}")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(logTag, "Failed to delete distorted file: ${result.file.absolutePath}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
distortedFiles = emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cancelProcessing() {
|
||||||
|
processingJob?.cancel()
|
||||||
|
processingJob = null
|
||||||
|
processingPreset = null
|
||||||
|
}
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.actions.uploads
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VoiceAnonymizationSection(
|
||||||
|
selectedPreset: VoicePreset,
|
||||||
|
processingPreset: VoicePreset?,
|
||||||
|
onPresetSelected: (VoicePreset) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(Size5dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.voice_anonymize_title),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.voice_anonymize_description),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray,
|
||||||
|
maxLines = 3,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
VoicePresetSelector(
|
||||||
|
selectedPreset = selectedPreset,
|
||||||
|
processingPreset = processingPreset,
|
||||||
|
onPresetSelected = onPresetSelected,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+450
@@ -0,0 +1,450 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.actions.uploads
|
||||||
|
|
||||||
|
import android.media.MediaCodec
|
||||||
|
import android.media.MediaCodecInfo
|
||||||
|
import android.media.MediaExtractor
|
||||||
|
import android.media.MediaFormat
|
||||||
|
import android.media.MediaMuxer
|
||||||
|
import android.util.Log
|
||||||
|
import be.tarsos.dsp.AudioDispatcher
|
||||||
|
import be.tarsos.dsp.AudioEvent
|
||||||
|
import be.tarsos.dsp.AudioProcessor
|
||||||
|
import be.tarsos.dsp.WaveformSimilarityBasedOverlapAdd
|
||||||
|
import be.tarsos.dsp.io.TarsosDSPAudioFloatConverter
|
||||||
|
import be.tarsos.dsp.io.TarsosDSPAudioFormat
|
||||||
|
import be.tarsos.dsp.resample.RateTransposer
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
data class AnonymizedResult(
|
||||||
|
val file: File,
|
||||||
|
val waveform: List<Float>,
|
||||||
|
val duration: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
class VoiceAnonymizer {
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "VoiceAnonymizer"
|
||||||
|
private const val CHANNELS = 1
|
||||||
|
private const val BIT_RATE = 128000
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun anonymize(
|
||||||
|
inputFile: File,
|
||||||
|
preset: VoicePreset,
|
||||||
|
onProgress: (Float) -> Unit = {},
|
||||||
|
): Result<AnonymizedResult> =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if (preset == VoicePreset.NONE) {
|
||||||
|
return@withContext Result.failure(
|
||||||
|
IllegalArgumentException("Cannot anonymize with NONE preset"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val outputFile = createOutputFile(inputFile, preset)
|
||||||
|
val (pcmData, sampleRate, duration) =
|
||||||
|
decodeAudioToPcm(inputFile) { progress ->
|
||||||
|
onProgress(progress * 0.3f)
|
||||||
|
}
|
||||||
|
|
||||||
|
val processedPcm =
|
||||||
|
processPcmWithTarsos(pcmData, preset, sampleRate) { progress ->
|
||||||
|
onProgress(0.3f + progress * 0.4f)
|
||||||
|
}
|
||||||
|
|
||||||
|
val waveform = extractWaveform(processedPcm, sampleRate)
|
||||||
|
|
||||||
|
encodePcmToAac(processedPcm, sampleRate, outputFile) { progress ->
|
||||||
|
onProgress(0.7f + progress * 0.3f)
|
||||||
|
}
|
||||||
|
|
||||||
|
onProgress(1f)
|
||||||
|
Result.success(AnonymizedResult(outputFile, waveform, duration))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to anonymize audio", e)
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createOutputFile(
|
||||||
|
inputFile: File,
|
||||||
|
preset: VoicePreset,
|
||||||
|
): File {
|
||||||
|
val baseName = inputFile.nameWithoutExtension
|
||||||
|
val presetSuffix = preset.name.lowercase()
|
||||||
|
return File(inputFile.parentFile, "${baseName}_$presetSuffix.mp4")
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class DecodedAudio(
|
||||||
|
val pcmData: FloatArray,
|
||||||
|
val sampleRate: Int,
|
||||||
|
val duration: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun decodeAudioToPcm(
|
||||||
|
inputFile: File,
|
||||||
|
onProgress: (Float) -> Unit,
|
||||||
|
): DecodedAudio {
|
||||||
|
val extractor = MediaExtractor()
|
||||||
|
var decoder: MediaCodec? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
extractor.setDataSource(inputFile.absolutePath)
|
||||||
|
|
||||||
|
var audioTrackIndex = -1
|
||||||
|
var format: MediaFormat? = null
|
||||||
|
for (i in 0 until extractor.trackCount) {
|
||||||
|
val trackFormat = extractor.getTrackFormat(i)
|
||||||
|
val mime = trackFormat.getString(MediaFormat.KEY_MIME)
|
||||||
|
if (mime?.startsWith("audio/") == true) {
|
||||||
|
audioTrackIndex = i
|
||||||
|
format = trackFormat
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioTrackIndex == -1 || format == null) {
|
||||||
|
throw IllegalStateException("No audio track found in file")
|
||||||
|
}
|
||||||
|
|
||||||
|
extractor.selectTrack(audioTrackIndex)
|
||||||
|
val mime = format.getString(MediaFormat.KEY_MIME) ?: "audio/mp4a-latm"
|
||||||
|
val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)
|
||||||
|
val durationUs = format.getLong(MediaFormat.KEY_DURATION)
|
||||||
|
val duration = (durationUs / 1_000_000).toInt()
|
||||||
|
|
||||||
|
decoder = MediaCodec.createDecoderByType(mime)
|
||||||
|
decoder.configure(format, null, null, 0)
|
||||||
|
decoder.start()
|
||||||
|
|
||||||
|
val pcmSamples = mutableListOf<Float>()
|
||||||
|
val bufferInfo = MediaCodec.BufferInfo()
|
||||||
|
var inputDone = false
|
||||||
|
var outputDone = false
|
||||||
|
|
||||||
|
while (!outputDone && currentCoroutineContext().isActive) {
|
||||||
|
if (!inputDone) {
|
||||||
|
val inputBufferIndex = decoder.dequeueInputBuffer(10000)
|
||||||
|
if (inputBufferIndex >= 0) {
|
||||||
|
val inputBuffer = decoder.getInputBuffer(inputBufferIndex)!!
|
||||||
|
val sampleSize = extractor.readSampleData(inputBuffer, 0)
|
||||||
|
if (sampleSize < 0) {
|
||||||
|
decoder.queueInputBuffer(
|
||||||
|
inputBufferIndex,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
||||||
|
)
|
||||||
|
inputDone = true
|
||||||
|
} else {
|
||||||
|
val presentationTimeUs = extractor.sampleTime
|
||||||
|
decoder.queueInputBuffer(
|
||||||
|
inputBufferIndex,
|
||||||
|
0,
|
||||||
|
sampleSize,
|
||||||
|
presentationTimeUs,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
extractor.advance()
|
||||||
|
if (durationUs > 0) {
|
||||||
|
onProgress((presentationTimeUs.toFloat() / durationUs).coerceIn(0f, 1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputBufferIndex = decoder.dequeueOutputBuffer(bufferInfo, 10000)
|
||||||
|
if (outputBufferIndex >= 0) {
|
||||||
|
val outputBuffer = decoder.getOutputBuffer(outputBufferIndex)!!
|
||||||
|
val shortBuffer = outputBuffer.order(ByteOrder.nativeOrder()).asShortBuffer()
|
||||||
|
while (shortBuffer.hasRemaining()) {
|
||||||
|
pcmSamples.add(shortBuffer.get() / 32768f)
|
||||||
|
}
|
||||||
|
decoder.releaseOutputBuffer(outputBufferIndex, false)
|
||||||
|
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
|
||||||
|
outputDone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecodedAudio(pcmSamples.toFloatArray(), sampleRate, duration)
|
||||||
|
} finally {
|
||||||
|
decoder?.stop()
|
||||||
|
decoder?.release()
|
||||||
|
extractor.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processPcmWithTarsos(
|
||||||
|
pcmData: FloatArray,
|
||||||
|
preset: VoicePreset,
|
||||||
|
sampleRate: Int,
|
||||||
|
onProgress: (Float) -> Unit,
|
||||||
|
): FloatArray {
|
||||||
|
val baseFactor = preset.pitchFactor
|
||||||
|
val factor =
|
||||||
|
when (preset) {
|
||||||
|
VoicePreset.DEEP, VoicePreset.HIGH -> {
|
||||||
|
// Add ±10% random variation
|
||||||
|
val randomShift = 0.9 + (Math.random() * 0.2)
|
||||||
|
baseFactor * randomShift
|
||||||
|
}
|
||||||
|
else -> baseFactor
|
||||||
|
}
|
||||||
|
val processedSamples = mutableListOf<Float>()
|
||||||
|
val totalSamples = pcmData.size
|
||||||
|
|
||||||
|
val wsola =
|
||||||
|
WaveformSimilarityBasedOverlapAdd(
|
||||||
|
WaveformSimilarityBasedOverlapAdd.Parameters.musicDefaults(
|
||||||
|
factor,
|
||||||
|
sampleRate.toDouble(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val rateTransposer = RateTransposer(factor)
|
||||||
|
|
||||||
|
val bufferSize = wsola.inputBufferSize
|
||||||
|
val overlap = wsola.overlap
|
||||||
|
|
||||||
|
val tarsosDspFormat =
|
||||||
|
TarsosDSPAudioFormat(
|
||||||
|
sampleRate.toFloat(),
|
||||||
|
16,
|
||||||
|
1,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
val collector =
|
||||||
|
object : AudioProcessor {
|
||||||
|
override fun process(audioEvent: AudioEvent): Boolean {
|
||||||
|
val buffer = audioEvent.floatBuffer
|
||||||
|
for (i in 0 until audioEvent.bufferSize) {
|
||||||
|
processedSamples.add(buffer[i])
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processingFinished() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dispatcher =
|
||||||
|
AudioDispatcher(
|
||||||
|
FloatArrayAudioInputStream(pcmData, tarsosDspFormat, pcmData.size.toLong()),
|
||||||
|
bufferSize,
|
||||||
|
overlap,
|
||||||
|
)
|
||||||
|
|
||||||
|
wsola.setDispatcher(dispatcher)
|
||||||
|
dispatcher.addAudioProcessor(wsola)
|
||||||
|
dispatcher.addAudioProcessor(rateTransposer)
|
||||||
|
dispatcher.addAudioProcessor(collector)
|
||||||
|
|
||||||
|
var samplesProcessed = 0
|
||||||
|
val progressProcessor =
|
||||||
|
object : AudioProcessor {
|
||||||
|
override fun process(audioEvent: AudioEvent): Boolean {
|
||||||
|
samplesProcessed += audioEvent.bufferSize
|
||||||
|
onProgress((samplesProcessed.toFloat() / totalSamples).coerceIn(0f, 1f))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processingFinished() {}
|
||||||
|
}
|
||||||
|
dispatcher.addAudioProcessor(progressProcessor)
|
||||||
|
|
||||||
|
dispatcher.run()
|
||||||
|
|
||||||
|
return processedSamples.toFloatArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractWaveform(
|
||||||
|
pcmData: FloatArray,
|
||||||
|
sampleRate: Int,
|
||||||
|
): List<Float> {
|
||||||
|
val waveform = mutableListOf<Float>()
|
||||||
|
var offset = 0
|
||||||
|
|
||||||
|
while (offset < pcmData.size) {
|
||||||
|
val end = minOf(offset + sampleRate, pcmData.size)
|
||||||
|
var maxAmplitude = 0f
|
||||||
|
for (i in offset until end) {
|
||||||
|
val amplitude = abs(pcmData[i])
|
||||||
|
if (amplitude > maxAmplitude) {
|
||||||
|
maxAmplitude = amplitude
|
||||||
|
}
|
||||||
|
}
|
||||||
|
waveform.add(maxAmplitude * 32768f)
|
||||||
|
offset += sampleRate
|
||||||
|
}
|
||||||
|
|
||||||
|
return waveform
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun encodePcmToAac(
|
||||||
|
pcmData: FloatArray,
|
||||||
|
sampleRate: Int,
|
||||||
|
outputFile: File,
|
||||||
|
onProgress: (Float) -> Unit,
|
||||||
|
) {
|
||||||
|
val format =
|
||||||
|
MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, CHANNELS)
|
||||||
|
format.setInteger(
|
||||||
|
MediaFormat.KEY_AAC_PROFILE,
|
||||||
|
MediaCodecInfo.CodecProfileLevel.AACObjectLC,
|
||||||
|
)
|
||||||
|
format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE)
|
||||||
|
|
||||||
|
val encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC)
|
||||||
|
val muxer = MediaMuxer(outputFile.absolutePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
|
||||||
|
var muxerStarted = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
|
||||||
|
encoder.start()
|
||||||
|
|
||||||
|
var audioTrackIndex = -1
|
||||||
|
val bufferInfo = MediaCodec.BufferInfo()
|
||||||
|
var inputOffset = 0
|
||||||
|
var inputDone = false
|
||||||
|
var outputDone = false
|
||||||
|
val totalSamples = pcmData.size
|
||||||
|
|
||||||
|
while (!outputDone) {
|
||||||
|
if (!inputDone) {
|
||||||
|
val inputBufferIndex = encoder.dequeueInputBuffer(10000)
|
||||||
|
if (inputBufferIndex >= 0) {
|
||||||
|
val inputBuffer = encoder.getInputBuffer(inputBufferIndex)!!
|
||||||
|
inputBuffer.clear()
|
||||||
|
|
||||||
|
val samplesToWrite = minOf((inputBuffer.capacity() / 2), pcmData.size - inputOffset)
|
||||||
|
if (samplesToWrite <= 0) {
|
||||||
|
encoder.queueInputBuffer(
|
||||||
|
inputBufferIndex,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
MediaCodec.BUFFER_FLAG_END_OF_STREAM,
|
||||||
|
)
|
||||||
|
inputDone = true
|
||||||
|
} else {
|
||||||
|
for (i in 0 until samplesToWrite) {
|
||||||
|
val sample =
|
||||||
|
(pcmData[inputOffset + i] * 32767)
|
||||||
|
.toInt()
|
||||||
|
.coerceIn(-32768, 32767)
|
||||||
|
.toShort()
|
||||||
|
inputBuffer.putShort(sample)
|
||||||
|
}
|
||||||
|
val presentationTimeUs = (inputOffset * 1_000_000L) / sampleRate
|
||||||
|
encoder.queueInputBuffer(
|
||||||
|
inputBufferIndex,
|
||||||
|
0,
|
||||||
|
inputBuffer.position(),
|
||||||
|
presentationTimeUs,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
inputOffset += samplesToWrite
|
||||||
|
onProgress(inputOffset.toFloat() / totalSamples)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputBufferIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000)
|
||||||
|
when {
|
||||||
|
outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {
|
||||||
|
audioTrackIndex = muxer.addTrack(encoder.outputFormat)
|
||||||
|
muxer.start()
|
||||||
|
muxerStarted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
outputBufferIndex >= 0 -> {
|
||||||
|
val outputBuffer = encoder.getOutputBuffer(outputBufferIndex)!!
|
||||||
|
if (muxerStarted && bufferInfo.size > 0) {
|
||||||
|
outputBuffer.position(bufferInfo.offset)
|
||||||
|
outputBuffer.limit(bufferInfo.offset + bufferInfo.size)
|
||||||
|
muxer.writeSampleData(audioTrackIndex, outputBuffer, bufferInfo)
|
||||||
|
}
|
||||||
|
encoder.releaseOutputBuffer(outputBufferIndex, false)
|
||||||
|
if (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM != 0) {
|
||||||
|
outputDone = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
encoder.stop()
|
||||||
|
encoder.release()
|
||||||
|
if (muxerStarted) {
|
||||||
|
muxer.stop()
|
||||||
|
}
|
||||||
|
muxer.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FloatArrayAudioInputStream(
|
||||||
|
private val floatArray: FloatArray,
|
||||||
|
private val format: TarsosDSPAudioFormat,
|
||||||
|
private val frameLength: Long,
|
||||||
|
) : be.tarsos.dsp.io.TarsosDSPAudioInputStream {
|
||||||
|
private var position = 0
|
||||||
|
|
||||||
|
override fun getFormat(): TarsosDSPAudioFormat = format
|
||||||
|
|
||||||
|
override fun getFrameLength(): Long = frameLength
|
||||||
|
|
||||||
|
override fun read(
|
||||||
|
buffer: ByteArray,
|
||||||
|
offset: Int,
|
||||||
|
length: Int,
|
||||||
|
): Int {
|
||||||
|
val converter = TarsosDSPAudioFloatConverter.getConverter(format)
|
||||||
|
val floatBuffer = FloatArray(length / 2)
|
||||||
|
val samplesToRead = minOf(floatBuffer.size, floatArray.size - position)
|
||||||
|
|
||||||
|
if (samplesToRead <= 0) return -1
|
||||||
|
|
||||||
|
System.arraycopy(floatArray, position, floatBuffer, 0, samplesToRead)
|
||||||
|
position += samplesToRead
|
||||||
|
|
||||||
|
converter.toByteArray(floatBuffer, samplesToRead, buffer, offset)
|
||||||
|
return samplesToRead * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun skip(bytesToSkip: Long): Long {
|
||||||
|
val samplesToSkip = (bytesToSkip / 2).toInt()
|
||||||
|
val actualSkip = minOf(samplesToSkip, floatArray.size - position)
|
||||||
|
position += actualSkip
|
||||||
|
return actualSkip.toLong() * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.actions.uploads
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
|
||||||
|
enum class VoicePreset(
|
||||||
|
val pitchFactor: Double,
|
||||||
|
val labelRes: Int,
|
||||||
|
) {
|
||||||
|
NONE(1.0, R.string.voice_preset_none),
|
||||||
|
DEEP(1.4, R.string.voice_preset_deep),
|
||||||
|
HIGH(0.75, R.string.voice_preset_high),
|
||||||
|
NEUTRAL(0.9, R.string.voice_preset_neutral),
|
||||||
|
}
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.actions.uploads
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun VoicePresetSelector(
|
||||||
|
selectedPreset: VoicePreset,
|
||||||
|
processingPreset: VoicePreset?,
|
||||||
|
onPresetSelected: (VoicePreset) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val isProcessing = processingPreset != null
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||||
|
) {
|
||||||
|
VoicePreset.entries.forEach { preset ->
|
||||||
|
val isSelected = preset == selectedPreset
|
||||||
|
val isThisProcessing = preset == processingPreset
|
||||||
|
val isEnabled = !isProcessing || preset == VoicePreset.NONE
|
||||||
|
|
||||||
|
FilterChip(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = { if (isEnabled) onPresetSelected(preset) },
|
||||||
|
enabled = isEnabled,
|
||||||
|
label = {
|
||||||
|
if (isThisProcessing) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(stringRes(context, preset.labelRes))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors =
|
||||||
|
FilterChipDefaults.filterChipColors(
|
||||||
|
selectedContainerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
selectedLabelColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -375,7 +375,7 @@ fun CustomEmojiChecker(
|
|||||||
onEmojiText: @Composable (ImmutableList<CustomEmoji.Renderable>) -> Unit,
|
onEmojiText: @Composable (ImmutableList<CustomEmoji.Renderable>) -> Unit,
|
||||||
) {
|
) {
|
||||||
val mayContainEmoji by remember(text, tags) {
|
val mayContainEmoji by remember(text, tags) {
|
||||||
mutableStateOf(CustomEmoji.fastMightContainEmoji(text, tags))
|
mutableStateOf(CustomEmoji.fastMightContainEmoji(text, tags?.lists))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mayContainEmoji) {
|
if (mayContainEmoji) {
|
||||||
@@ -385,7 +385,7 @@ fun CustomEmojiChecker(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(text, tags) {
|
LaunchedEffect(text, tags) {
|
||||||
val newEmojiList = CustomEmoji.assembleAnnotatedList(text, tags)
|
val newEmojiList = CustomEmoji.assembleAnnotatedList(text, tags?.lists)
|
||||||
if (newEmojiList != null) {
|
if (newEmojiList != null) {
|
||||||
emojiList = newEmojiList
|
emojiList = newEmojiList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.dal
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.ListChange
|
import com.vitorpamplona.amethyst.commons.model.ListChange
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
interface ChangesFlowFilter<T> : IAdditiveFeedFilter<T> {
|
interface ChangesFlowFilter<T> : IAdditiveFeedFilter<T> {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
|
// Re-export from commons for backwards compatibility
|
||||||
|
typealias IFeedFilter<T> = com.vitorpamplona.amethyst.commons.ui.feeds.IFeedFilter<T>
|
||||||
|
typealias IAdditiveFeedFilter<T> = com.vitorpamplona.amethyst.commons.ui.feeds.IAdditiveFeedFilter<T>
|
||||||
|
typealias FeedFilter<T> = com.vitorpamplona.amethyst.commons.ui.feeds.FeedFilter<T>
|
||||||
|
typealias AdditiveFeedFilter<T> = com.vitorpamplona.amethyst.commons.ui.feeds.AdditiveFeedFilter<T>
|
||||||
+1
-1
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.ui.feeds
|
|||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.BundledInsert
|
import com.vitorpamplona.amethyst.service.BundledInsert
|
||||||
import com.vitorpamplona.amethyst.service.BundledUpdate
|
import com.vitorpamplona.amethyst.service.BundledUpdate
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.feeds
|
package com.vitorpamplona.amethyst.ui.feeds
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.feeds
|
||||||
|
|
||||||
|
// Re-export from commons for backwards compatibility - import everything
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState as CommonsFeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState as CommonsFeedState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.InvalidatableContent as CommonsInvalidatableContent
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.LoadedFeedState as CommonsLoadedFeedState
|
||||||
|
|
||||||
|
typealias FeedState = CommonsFeedState
|
||||||
|
typealias LoadedFeedState<T> = CommonsLoadedFeedState<T>
|
||||||
|
typealias InvalidatableContent = CommonsInvalidatableContent
|
||||||
|
typealias FeedContentState = CommonsFeedContentState
|
||||||
+3
-3
@@ -20,13 +20,13 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.navigation.routes
|
package com.vitorpamplona.amethyst.ui.navigation.routes
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
||||||
|
|||||||
@@ -32,13 +32,13 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteOts
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteOts
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserStatuses
|
||||||
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedStateAsync
|
import com.vitorpamplona.amethyst.commons.compose.produceCachedStateAsync
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelPicture
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelPicture
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeCommunityApprovalNeedStatus
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeCommunityApprovalNeedStatus
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEdits
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEdits
|
||||||
|
|||||||
+3
-1
@@ -20,14 +20,16 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions
|
package com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
|
||||||
|
@Stable
|
||||||
class EmojiSuggestionState(
|
class EmojiSuggestionState(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
) {
|
) {
|
||||||
|
|||||||
+18
-20
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.note.creators.emojiSuggestions
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
@@ -43,10 +42,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.gallery.UrlImageView
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
@@ -57,7 +55,6 @@ fun ShowEmojiSuggestionList(
|
|||||||
emojiSuggestions: EmojiSuggestionState,
|
emojiSuggestions: EmojiSuggestionState,
|
||||||
onSelect: (EmojiPackState.EmojiMedia) -> Unit,
|
onSelect: (EmojiPackState.EmojiMedia) -> Unit,
|
||||||
onFullSize: (EmojiPackState.EmojiMedia) -> Unit,
|
onFullSize: (EmojiPackState.EmojiMedia) -> Unit,
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
modifier: Modifier = Modifier.heightIn(0.dp, 200.dp),
|
modifier: Modifier = Modifier.heightIn(0.dp, 200.dp),
|
||||||
) {
|
) {
|
||||||
val suggestions by emojiSuggestions.results.collectAsStateWithLifecycle(emptyList())
|
val suggestions by emojiSuggestions.results.collectAsStateWithLifecycle(emptyList())
|
||||||
@@ -79,22 +76,23 @@ fun ShowEmojiSuggestionList(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = spacedBy(Size10dp),
|
horizontalArrangement = spacedBy(Size10dp),
|
||||||
) {
|
) {
|
||||||
Box(Size40Modifier) {
|
AsyncImage(
|
||||||
UrlImageView(it.link, accountViewModel)
|
it.link,
|
||||||
}
|
contentDescription = it.code,
|
||||||
|
modifier = Size40Modifier,
|
||||||
|
)
|
||||||
Text(it.code, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
Text(it.code, fontWeight = FontWeight.Bold, modifier = Modifier.weight(1f))
|
||||||
Box(Size40Modifier, contentAlignment = Alignment.Center) {
|
IconButton(
|
||||||
IconButton(
|
modifier = Size40Modifier,
|
||||||
onClick = {
|
onClick = {
|
||||||
onFullSize(it)
|
onFullSize(it)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.OpenInFull,
|
imageVector = Icons.Outlined.OpenInFull,
|
||||||
contentDescription = stringRes(R.string.use_direct_url),
|
contentDescription = stringRes(R.string.use_direct_url),
|
||||||
modifier = Modifier.size(20.dp),
|
modifier = Modifier.size(20.dp),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
|
|||||||
+5
-5
@@ -33,11 +33,11 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.commons.compose.currentWord
|
import com.vitorpamplona.amethyst.commons.compose.currentWord
|
||||||
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
||||||
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
|
|
||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||||
import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator
|
import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator
|
||||||
@@ -435,7 +435,7 @@ open class CommentPostViewModel :
|
|||||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let {
|
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let {
|
||||||
EmojiUrlTag(
|
EmojiUrlTag(
|
||||||
it.code,
|
it.code,
|
||||||
it.link.url,
|
it.link,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,11 +634,11 @@ open class CommentPostViewModel :
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun autocompleteWithEmojiUrl(item: EmojiPackState.EmojiMedia) {
|
open fun autocompleteWithEmojiUrl(item: EmojiPackState.EmojiMedia) {
|
||||||
val wordToInsert = item.link.url + " "
|
val wordToInsert = item.link + " "
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
iMetaAttachments.downloadAndPrepare(item.link) {
|
||||||
Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForImage(item.link.url)
|
Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForImage(item.link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-1
@@ -357,7 +357,6 @@ private fun GenericCommentPostBody(
|
|||||||
it,
|
it,
|
||||||
postViewModel::autocompleteWithEmoji,
|
postViewModel::autocompleteWithEmoji,
|
||||||
postViewModel::autocompleteWithEmojiUrl,
|
postViewModel::autocompleteWithEmojiUrl,
|
||||||
accountViewModel,
|
|
||||||
modifier = Modifier.heightIn(0.dp, 300.dp),
|
modifier = Modifier.heightIn(0.dp, 300.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,13 @@ fun RenderTextEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this is an audio-only event (content is just an audio URL with waveform IMeta)
|
||||||
|
val isAudioOnly = remember(noteEvent) { noteEvent.isAudioOnlyContent() }
|
||||||
|
if (isAudioOnly) {
|
||||||
|
RenderAudioFromIMeta(note, accountViewModel, nav)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
LoadDecryptedContent(
|
LoadDecryptedContent(
|
||||||
note,
|
note,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
|||||||
@@ -69,8 +69,11 @@ import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size75Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size75Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.VoiceHeightModifier
|
import com.vitorpamplona.amethyst.ui.theme.VoiceHeightModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
|
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
|
||||||
import com.vitorpamplona.quartz.nip14Subject.subject
|
import com.vitorpamplona.quartz.nip14Subject.subject
|
||||||
|
import com.vitorpamplona.quartz.nip92IMeta.imetas
|
||||||
|
import com.vitorpamplona.quartz.nipA0VoiceMessages.AudioMeta
|
||||||
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
|
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -107,6 +110,32 @@ fun VoiceHeader(
|
|||||||
?.let { WaveformData(it) }
|
?.let { WaveformData(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RenderAudioWithWaveform(
|
||||||
|
mediaUrl = media,
|
||||||
|
title = noteEvent.subject(),
|
||||||
|
mimeType = null,
|
||||||
|
waveform = waveform,
|
||||||
|
note = note,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared composable for rendering audio with waveform visualization.
|
||||||
|
* Used by both VoiceHeader (for BaseVoiceEvent) and RenderAudioFromIMeta (for other events with audio IMeta).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun RenderAudioWithWaveform(
|
||||||
|
mediaUrl: String,
|
||||||
|
title: String?,
|
||||||
|
mimeType: String?,
|
||||||
|
waveform: WaveformData?,
|
||||||
|
note: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val noteEvent = note.event ?: return
|
||||||
val callbackUri = remember(note) { note.toNostrUri() }
|
val callbackUri = remember(note) { note.toNostrUri() }
|
||||||
|
|
||||||
Column(modifier = MaxWidthPaddingTop5dp, horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(modifier = MaxWidthPaddingTop5dp, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
@@ -114,14 +143,14 @@ fun VoiceHeader(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
GetMediaItem(
|
GetMediaItem(
|
||||||
videoUri = media,
|
videoUri = mediaUrl,
|
||||||
title = noteEvent.subject(),
|
title = title,
|
||||||
artworkUri = null,
|
artworkUri = null,
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
callbackUri = callbackUri,
|
callbackUri = callbackUri,
|
||||||
mimeType = null,
|
mimeType = mimeType,
|
||||||
aspectRatio = null,
|
aspectRatio = null,
|
||||||
proxyPort = accountViewModel.httpClientBuilder.proxyPortForVideo(media),
|
proxyPort = accountViewModel.httpClientBuilder.proxyPortForVideo(mediaUrl),
|
||||||
keepPlaying = false,
|
keepPlaying = false,
|
||||||
waveformData = waveform,
|
waveformData = waveform,
|
||||||
) { mediaItem ->
|
) { mediaItem ->
|
||||||
@@ -168,7 +197,7 @@ fun RenderVoicePlayer(
|
|||||||
factory = { context: Context ->
|
factory = { context: Context ->
|
||||||
PlayerView(context).apply {
|
PlayerView(context).apply {
|
||||||
player = controllerState.controller
|
player = controllerState.controller
|
||||||
// if we alrady know the size of the frame, this forces the player to stay in the size
|
// if we already know the size of the frame, this forces the player to stay in the size
|
||||||
layoutParams =
|
layoutParams =
|
||||||
FrameLayout.LayoutParams(
|
FrameLayout.LayoutParams(
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
@@ -263,3 +292,54 @@ fun PlayPauseButton(controllerState: MediaControllerState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts AudioMeta from an event's IMeta tags if it has audio content with waveform.
|
||||||
|
* Returns the first audio IMeta that has a waveform, or null if none found.
|
||||||
|
*/
|
||||||
|
fun Event.getAudioMetaWithWaveform(): AudioMeta? {
|
||||||
|
val audioMetas = imetas().map { AudioMeta.parse(it) }
|
||||||
|
return audioMetas.firstOrNull { meta ->
|
||||||
|
meta.waveform != null &&
|
||||||
|
(meta.mimeType == null || meta.mimeType?.startsWith("audio/") == true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the event content is primarily an audio attachment (content is just the audio URL).
|
||||||
|
*/
|
||||||
|
fun Event.isAudioOnlyContent(): Boolean {
|
||||||
|
val audioMeta = getAudioMetaWithWaveform() ?: return false
|
||||||
|
return content.trim() == audioMeta.url
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders audio with waveform for any event type that has audio IMeta attachment.
|
||||||
|
* This allows KIND 1 (TextNoteEvent) and KIND 1111 (CommentEvent) with voice
|
||||||
|
* attachments to display the same waveform UI as VoiceEvent.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun RenderAudioFromIMeta(
|
||||||
|
note: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val noteEvent = note.event ?: return
|
||||||
|
|
||||||
|
val audioMeta = remember(noteEvent) { noteEvent.getAudioMetaWithWaveform() } ?: return
|
||||||
|
|
||||||
|
val waveform =
|
||||||
|
remember(audioMeta) {
|
||||||
|
audioMeta.waveform?.let { WaveformData(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderAudioWithWaveform(
|
||||||
|
mediaUrl = audioMeta.url,
|
||||||
|
title = null,
|
||||||
|
mimeType = audioMeta.mimeType,
|
||||||
|
waveform = waveform,
|
||||||
|
note = note,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedLoaded
|
import com.vitorpamplona.amethyst.ui.feeds.FeedLoaded
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchScrollToTop
|
import com.vitorpamplona.amethyst.ui.feeds.WatchScrollToTop
|
||||||
|
|||||||
@@ -20,52 +20,17 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Stable
|
// Re-export from commons for backwards compatibility
|
||||||
abstract class FeedViewModel(
|
typealias FeedViewModel = com.vitorpamplona.amethyst.commons.viewmodels.FeedViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android-specific FeedViewModel base class that provides LocalCache as the cache provider.
|
||||||
|
* Subclasses can extend this to automatically use LocalCache without passing cacheProvider.
|
||||||
|
*/
|
||||||
|
abstract class AndroidFeedViewModel(
|
||||||
localFilter: FeedFilter<Note>,
|
localFilter: FeedFilter<Note>,
|
||||||
) : ViewModel(),
|
) : FeedViewModel(localFilter, LocalCache)
|
||||||
InvalidatableContent {
|
|
||||||
val feedState = FeedContentState(localFilter, viewModelScope)
|
|
||||||
|
|
||||||
override val isRefreshing = feedState.isRefreshing
|
|
||||||
|
|
||||||
fun sendToTop() = feedState.sendToTop()
|
|
||||||
|
|
||||||
suspend fun sentToTop() = feedState.sentToTop()
|
|
||||||
|
|
||||||
override fun invalidateData(ignoreIfDoing: Boolean) = feedState.invalidateData(ignoreIfDoing)
|
|
||||||
|
|
||||||
init {
|
|
||||||
Log.d("Init", "Starting new Model: ${this.javaClass.simpleName}")
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
LocalCache.live.newEventBundles.collect { newNotes ->
|
|
||||||
Log.d("Rendering Metrics", "Update feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}")
|
|
||||||
feedState.updateFeedWith(newNotes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
|
||||||
LocalCache.live.deletedEventBundles.collect { newNotes ->
|
|
||||||
Log.d("Rendering Metrics", "Delete from feeds: ${this@FeedViewModel.javaClass.simpleName} with ${newNotes.size}")
|
|
||||||
feedState.deleteFromFeed(newNotes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCleared() {
|
|
||||||
Log.d("Init", "OnCleared: ${this.javaClass.simpleName}")
|
|
||||||
super.onCleared()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.InvalidatableContent
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.BundledUpdate
|
import com.vitorpamplona.amethyst.service.BundledUpdate
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|||||||
+15
-14
@@ -20,11 +20,12 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.ChannelFeedContentState
|
import com.vitorpamplona.amethyst.ui.feeds.ChannelFeedContentState
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState
|
import com.vitorpamplona.amethyst.ui.screen.TopNavFilterState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListKnownFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListKnownFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListNewFeedFilter
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.dal.ChatroomListNewFeedFilter
|
||||||
@@ -50,28 +51,28 @@ class AccountFeedContentStates(
|
|||||||
val scope: CoroutineScope,
|
val scope: CoroutineScope,
|
||||||
) {
|
) {
|
||||||
val homeLive = ChannelFeedContentState(HomeLiveFilter(account), scope)
|
val homeLive = ChannelFeedContentState(HomeLiveFilter(account), scope)
|
||||||
val homeNewThreads = FeedContentState(HomeNewThreadFeedFilter(account), scope)
|
val homeNewThreads = FeedContentState(HomeNewThreadFeedFilter(account), scope, LocalCache)
|
||||||
val homeReplies = FeedContentState(HomeConversationsFeedFilter(account), scope)
|
val homeReplies = FeedContentState(HomeConversationsFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
val dmKnown = FeedContentState(ChatroomListKnownFeedFilter(account), scope)
|
val dmKnown = FeedContentState(ChatroomListKnownFeedFilter(account), scope, LocalCache)
|
||||||
val dmNew = FeedContentState(ChatroomListNewFeedFilter(account), scope)
|
val dmNew = FeedContentState(ChatroomListNewFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
val videoFeed = FeedContentState(VideoFeedFilter(account), scope)
|
val videoFeed = FeedContentState(VideoFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
val discoverFollowSets = FeedContentState(DiscoverFollowSetsFeedFilter(account), scope)
|
val discoverFollowSets = FeedContentState(DiscoverFollowSetsFeedFilter(account), scope, LocalCache)
|
||||||
val discoverReads = FeedContentState(DiscoverLongFormFeedFilter(account), scope)
|
val discoverReads = FeedContentState(DiscoverLongFormFeedFilter(account), scope, LocalCache)
|
||||||
val discoverMarketplace = FeedContentState(DiscoverMarketplaceFeedFilter(account), scope)
|
val discoverMarketplace = FeedContentState(DiscoverMarketplaceFeedFilter(account), scope, LocalCache)
|
||||||
val discoverDVMs = FeedContentState(DiscoverNIP89FeedFilter(account), scope)
|
val discoverDVMs = FeedContentState(DiscoverNIP89FeedFilter(account), scope, LocalCache)
|
||||||
val discoverLive = FeedContentState(DiscoverLiveFeedFilter(account), scope)
|
val discoverLive = FeedContentState(DiscoverLiveFeedFilter(account), scope, LocalCache)
|
||||||
val discoverCommunities = FeedContentState(DiscoverCommunityFeedFilter(account), scope)
|
val discoverCommunities = FeedContentState(DiscoverCommunityFeedFilter(account), scope, LocalCache)
|
||||||
val discoverPublicChats = FeedContentState(DiscoverChatFeedFilter(account), scope)
|
val discoverPublicChats = FeedContentState(DiscoverChatFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
val notifications = CardFeedContentState(NotificationFeedFilter(account), scope)
|
val notifications = CardFeedContentState(NotificationFeedFilter(account), scope)
|
||||||
val notificationSummary = NotificationSummaryState(account)
|
val notificationSummary = NotificationSummaryState(account)
|
||||||
|
|
||||||
val feedListOptions = TopNavFilterState(account, scope)
|
val feedListOptions = TopNavFilterState(account, scope)
|
||||||
|
|
||||||
val drafts = FeedContentState(DraftEventsFeedFilter(account), scope)
|
val drafts = FeedContentState(DraftEventsFeedFilter(account), scope, LocalCache)
|
||||||
|
|
||||||
suspend fun init() {
|
suspend fun init() {
|
||||||
notificationSummary.initializeSuspend()
|
notificationSummary.initializeSuspend()
|
||||||
|
|||||||
+38
-65
@@ -28,7 +28,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -41,6 +40,10 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
|
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCache
|
||||||
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCacheAsync
|
import com.vitorpamplona.amethyst.commons.compose.GenericBaseCacheAsync
|
||||||
import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers
|
import com.vitorpamplona.amethyst.commons.model.LiveHiddenUsers
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.logTime
|
import com.vitorpamplona.amethyst.logTime
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.AccountSettings
|
import com.vitorpamplona.amethyst.model.AccountSettings
|
||||||
@@ -50,9 +53,6 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
import com.vitorpamplona.amethyst.model.UiSettingsFlow
|
||||||
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
import com.vitorpamplona.amethyst.model.UrlCachedPreviewer
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.observables.CreatedAtComparator
|
import com.vitorpamplona.amethyst.model.observables.CreatedAtComparator
|
||||||
import com.vitorpamplona.amethyst.model.privacyOptions.EmptyRoleBasedHttpClientBuilder
|
import com.vitorpamplona.amethyst.model.privacyOptions.EmptyRoleBasedHttpClientBuilder
|
||||||
import com.vitorpamplona.amethyst.model.privacyOptions.IRoleBasedHttpClientBuilder
|
import com.vitorpamplona.amethyst.model.privacyOptions.IRoleBasedHttpClientBuilder
|
||||||
@@ -67,15 +67,10 @@ import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
|||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.RelaySubscriptionsCoordinator
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
||||||
import com.vitorpamplona.amethyst.service.uploads.CompressorQuality
|
|
||||||
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
|
||||||
import com.vitorpamplona.amethyst.service.uploads.UploadingState
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.Dao
|
import com.vitorpamplona.amethyst.ui.actions.Dao
|
||||||
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.RecordingResult
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||||
import com.vitorpamplona.amethyst.ui.components.toasts.ToastManager
|
import com.vitorpamplona.amethyst.ui.components.toasts.ToastManager
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
||||||
@@ -107,6 +102,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.people.PubKeyReferenceTag
|
|||||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||||
import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder
|
import com.vitorpamplona.quartz.nip03Timestamp.EmptyOtsResolverBuilder
|
||||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||||
|
import com.vitorpamplona.quartz.nip10Notes.tags.MarkedETag
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKeyable
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||||
@@ -134,7 +130,6 @@ import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
|||||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
|
||||||
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||||
import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent
|
|
||||||
import com.vitorpamplona.quartz.utils.Hex
|
import com.vitorpamplona.quartz.utils.Hex
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
@@ -390,7 +385,7 @@ class AccountViewModel(
|
|||||||
note.flow().author(),
|
note.flow().author(),
|
||||||
note.flow().metadata.stateFlow,
|
note.flow().metadata.stateFlow,
|
||||||
note.flow().reports.stateFlow,
|
note.flow().reports.stateFlow,
|
||||||
) { hiddenUsers, followingUsers, autor, metadata, reports ->
|
) { hiddenUsers, followingUsers, _, metadata, _ ->
|
||||||
emit(isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.authors))
|
emit(isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.authors))
|
||||||
}.onStart {
|
}.onStart {
|
||||||
emit(
|
emit(
|
||||||
@@ -706,9 +701,10 @@ class AccountViewModel(
|
|||||||
fun report(
|
fun report(
|
||||||
user: User,
|
user: User,
|
||||||
type: ReportType,
|
type: ReportType,
|
||||||
|
content: String = "",
|
||||||
) {
|
) {
|
||||||
launchSigner {
|
launchSigner {
|
||||||
account.report(user, type)
|
account.report(user, type, content)
|
||||||
account.hideUser(user.pubkeyHex)
|
account.hideUser(user.pubkeyHex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -998,6 +994,32 @@ class AccountViewModel(
|
|||||||
|
|
||||||
fun getNoteIfExists(hex: HexKey): Note? = LocalCache.getNoteIfExists(hex)
|
fun getNoteIfExists(hex: HexKey): Note? = LocalCache.getNoteIfExists(hex)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fixes author and relay hints in MarkedETag list by looking up notes from cache.
|
||||||
|
* This ensures reply tags have proper author pubkeys and relay hints for threading.
|
||||||
|
*/
|
||||||
|
fun fixReplyTagHints(tags: List<MarkedETag>) {
|
||||||
|
tags.forEach { tag ->
|
||||||
|
val note = getNoteIfExists(tag.eventId)
|
||||||
|
val cachedAuthor = note?.author?.pubkeyHex
|
||||||
|
val cachedRelay = note?.relayHintUrl()
|
||||||
|
|
||||||
|
// Fix author if missing or different from cached
|
||||||
|
if (tag.author.isNullOrBlank() && cachedAuthor != null) {
|
||||||
|
tag.author = cachedAuthor
|
||||||
|
} else if (cachedAuthor != null && tag.author != cachedAuthor) {
|
||||||
|
tag.author = cachedAuthor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix relay hint if missing or different from cached
|
||||||
|
if (tag.relay == null && cachedRelay != null) {
|
||||||
|
tag.relay = cachedRelay
|
||||||
|
} else if (cachedRelay != null && tag.relay != cachedRelay) {
|
||||||
|
tag.relay = cachedRelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getOrCreateAddressableNote(address: Address): AddressableNote = LocalCache.getOrCreateAddressableNote(address)
|
override suspend fun getOrCreateAddressableNote(address: Address): AddressableNote = LocalCache.getOrCreateAddressableNote(address)
|
||||||
|
|
||||||
fun getAddressableNoteIfExists(key: String): AddressableNote? = LocalCache.getAddressableNoteIfExists(key)
|
fun getAddressableNoteIfExists(key: String): AddressableNote? = LocalCache.getAddressableNoteIfExists(key)
|
||||||
@@ -1011,11 +1033,11 @@ class AccountViewModel(
|
|||||||
LocalCache.findLatestModificationForNote(note)
|
LocalCache.findLatestModificationForNote(note)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkGetOrCreatePublicChatChannel(key: HexKey): PublicChatChannel? = LocalCache.getOrCreatePublicChatChannel(key)
|
fun checkGetOrCreatePublicChatChannel(key: HexKey): PublicChatChannel = LocalCache.getOrCreatePublicChatChannel(key)
|
||||||
|
|
||||||
fun checkGetOrCreateLiveActivityChannel(key: Address): LiveActivitiesChannel? = LocalCache.getOrCreateLiveChannel(key)
|
fun checkGetOrCreateLiveActivityChannel(key: Address): LiveActivitiesChannel = LocalCache.getOrCreateLiveChannel(key)
|
||||||
|
|
||||||
fun checkGetOrCreateEphemeralChatChannel(key: RoomId): EphemeralChatChannel? = LocalCache.getOrCreateEphemeralChannel(key)
|
fun checkGetOrCreateEphemeralChatChannel(key: RoomId): EphemeralChatChannel = LocalCache.getOrCreateEphemeralChannel(key)
|
||||||
|
|
||||||
fun getPublicChatChannelIfExists(hex: HexKey) = LocalCache.getPublicChatChannelIfExists(hex)
|
fun getPublicChatChannelIfExists(hex: HexKey) = LocalCache.getPublicChatChannelIfExists(hex)
|
||||||
|
|
||||||
@@ -1148,53 +1170,6 @@ class AccountViewModel(
|
|||||||
super.onCleared()
|
super.onCleared()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendVoiceReply(
|
|
||||||
note: Note,
|
|
||||||
recording: RecordingResult,
|
|
||||||
context: Context,
|
|
||||||
) {
|
|
||||||
if (isWriteable()) {
|
|
||||||
val hint = note.toEventHint<BaseVoiceEvent>() ?: return
|
|
||||||
|
|
||||||
launchSigner {
|
|
||||||
val uploader = UploadOrchestrator()
|
|
||||||
val result =
|
|
||||||
uploader.upload(
|
|
||||||
uri = recording.file.toUri(),
|
|
||||||
mimeType = recording.mimeType,
|
|
||||||
alt = null,
|
|
||||||
contentWarningReason = null,
|
|
||||||
compressionQuality = CompressorQuality.UNCOMPRESSED,
|
|
||||||
server = account.settings.defaultFileServer,
|
|
||||||
account = account,
|
|
||||||
context = context,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (result is UploadingState.Finished && result.result is UploadOrchestrator.OrchestratorResult.ServerResult) {
|
|
||||||
account.sendVoiceReplyMessage(
|
|
||||||
result.result.url,
|
|
||||||
result.result.fileHeader.mimeType ?: recording.mimeType,
|
|
||||||
result.result.fileHeader.hash,
|
|
||||||
recording.duration,
|
|
||||||
recording.amplitudes,
|
|
||||||
hint,
|
|
||||||
)
|
|
||||||
} else if (result is UploadingState.Error) {
|
|
||||||
toastManager.toast(
|
|
||||||
R.string.failed_to_upload_media_no_details,
|
|
||||||
result.errorResource,
|
|
||||||
*result.params,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toastManager.toast(
|
|
||||||
R.string.read_only_user,
|
|
||||||
R.string.login_with_a_private_key_to_be_able_to_reply,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadThumb(
|
fun loadThumb(
|
||||||
context: Context,
|
context: Context,
|
||||||
thumbUri: String,
|
thumbUri: String,
|
||||||
@@ -1422,7 +1397,7 @@ class AccountViewModel(
|
|||||||
// First check if we have an actual response from the DVM in LocalCache
|
// First check if we have an actual response from the DVM in LocalCache
|
||||||
val response =
|
val response =
|
||||||
LocalCache.notes.maxOrNullOf(
|
LocalCache.notes.maxOrNullOf(
|
||||||
filter = { key, note ->
|
filter = { _, note ->
|
||||||
val noteEvent = note.event
|
val noteEvent = note.event
|
||||||
noteEvent is NIP90ContentDiscoveryResponseEvent &&
|
noteEvent is NIP90ContentDiscoveryResponseEvent &&
|
||||||
noteEvent.pubKey == pubkeyHex &&
|
noteEvent.pubKey == pubkeyHex &&
|
||||||
@@ -1531,8 +1506,6 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findUsersStartingWithSync(prefix: String) = LocalCache.findUsersStartingWith(prefix, account)
|
|
||||||
|
|
||||||
fun convertAccounts(loggedInAccounts: List<AccountInfo>?): Set<HexKey> =
|
fun convertAccounts(loggedInAccounts: List<AccountInfo>?): Set<HexKey> =
|
||||||
loggedInAccounts
|
loggedInAccounts
|
||||||
?.mapNotNull {
|
?.mapNotNull {
|
||||||
|
|||||||
+1
-1
@@ -20,10 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.privateChats.ChatroomList
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.privateChats.ChatroomList
|
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.IEvent
|
import com.vitorpamplona.quartz.nip01Core.core.IEvent
|
||||||
|
|||||||
+2
-2
@@ -24,12 +24,12 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AndroidFeedViewModel
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class BookmarkPrivateFeedViewModel(
|
class BookmarkPrivateFeedViewModel(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
) : FeedViewModel(BookmarkPrivateFeedFilter(account)) {
|
) : AndroidFeedViewModel(BookmarkPrivateFeedFilter(account)) {
|
||||||
class Factory(
|
class Factory(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
|
|||||||
+2
-2
@@ -24,12 +24,12 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AndroidFeedViewModel
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class BookmarkPublicFeedViewModel(
|
class BookmarkPublicFeedViewModel(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
) : FeedViewModel(BookmarkPublicFeedFilter(account)) {
|
) : AndroidFeedViewModel(BookmarkPublicFeedFilter(account)) {
|
||||||
class Factory(
|
class Factory(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
) : ViewModelProvider.Factory {
|
) : ViewModelProvider.Factory {
|
||||||
|
|||||||
+2
-2
@@ -30,12 +30,12 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.creators.draftTags.DraftTagState
|
import com.vitorpamplona.amethyst.ui.note.creators.draftTags.DraftTagState
|
||||||
|
|||||||
+5
-4
@@ -24,12 +24,13 @@ import androidx.compose.runtime.Stable
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.ListChange
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.InvalidatableContent
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.ListChange
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ChangesFlowFilter
|
import com.vitorpamplona.amethyst.ui.dal.ChangesFlowFilter
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
|
|
||||||
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
import com.vitorpamplona.quartz.nip17Dm.base.ChatroomKey
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -53,7 +54,7 @@ abstract class ListChangeFeedViewModel(
|
|||||||
localFilter: ChangesFlowFilter<Note>,
|
localFilter: ChangesFlowFilter<Note>,
|
||||||
) : ViewModel(),
|
) : ViewModel(),
|
||||||
InvalidatableContent {
|
InvalidatableContent {
|
||||||
val feedState = FeedContentState(localFilter, viewModelScope)
|
val feedState = FeedContentState(localFilter, viewModelScope, LocalCache)
|
||||||
|
|
||||||
override val isRefreshing = feedState.isRefreshing
|
override val isRefreshing = feedState.isRefreshing
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -32,11 +32,11 @@ import com.vitorpamplona.amethyst.Amethyst
|
|||||||
import com.vitorpamplona.amethyst.commons.compose.currentWord
|
import com.vitorpamplona.amethyst.commons.compose.currentWord
|
||||||
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
||||||
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
|
|
||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectedMedia
|
||||||
@@ -519,7 +519,7 @@ class ChatNewMessageViewModel :
|
|||||||
): List<EmojiUrlTag> {
|
): List<EmojiUrlTag> {
|
||||||
if (myEmojiSet == null) return emptyList()
|
if (myEmojiSet == null) return emptyList()
|
||||||
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
||||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link.url) }
|
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,11 +659,11 @@ class ChatNewMessageViewModel :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun autocompleteWithEmojiUrl(item: EmojiPackState.EmojiMedia) {
|
fun autocompleteWithEmojiUrl(item: EmojiPackState.EmojiMedia) {
|
||||||
val wordToInsert = item.link.url + " "
|
val wordToInsert = item.link + " "
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
iMetaAttachments.downloadAndPrepare(item.link) {
|
||||||
Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForImage(item.link.url)
|
Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForImage(item.link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-1
@@ -311,7 +311,6 @@ fun GroupDMScreenContent(
|
|||||||
it,
|
it,
|
||||||
postViewModel::autocompleteWithEmoji,
|
postViewModel::autocompleteWithEmoji,
|
||||||
postViewModel::autocompleteWithEmojiUrl,
|
postViewModel::autocompleteWithEmojiUrl,
|
||||||
accountViewModel,
|
|
||||||
Modifier.heightIn(0.dp, 300.dp),
|
Modifier.heightIn(0.dp, 300.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -127,7 +127,6 @@ fun PrivateMessageEditFieldRow(
|
|||||||
it,
|
it,
|
||||||
channelScreenModel::autocompleteWithEmoji,
|
channelScreenModel::autocompleteWithEmoji,
|
||||||
channelScreenModel::autocompleteWithEmojiUrl,
|
channelScreenModel::autocompleteWithEmojiUrl,
|
||||||
accountViewModel,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,8 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.dal
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ChangesFlowFilter
|
import com.vitorpamplona.amethyst.ui.dal.ChangesFlowFilter
|
||||||
|
|||||||
+1
-1
@@ -22,8 +22,8 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.dal
|
|||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.dal.ListChangeFeedViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.dal.ListChangeFeedViewModel
|
||||||
|
|
||||||
class ChannelFeedViewModel(
|
class ChannelFeedViewModel(
|
||||||
|
|||||||
+1
-1
@@ -20,8 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
import com.vitorpamplona.amethyst.service.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies.ChannelFromUserFilterSubAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies.ChannelFromUserFilterSubAssembler
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies.ChannelPublicFilterSubAssembler
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies.ChannelPublicFilterSubAssembler
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datas
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
import com.vitorpamplona.amethyst.service.relayClient.KeyDataSourceSubscription
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -20,10 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUserAndFollowListEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelQueryState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelQueryState
|
||||||
|
|||||||
+4
-4
@@ -20,10 +20,10 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelQueryState
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.ChannelQueryState
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.datasource.subassemblies
|
||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||||
|
|||||||
+1
-1
@@ -30,8 +30,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.note.produceStateIfNotNull
|
import com.vitorpamplona.amethyst.ui.note.produceStateIfNotNull
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
import com.vitorpamplona.quartz.experimental.ephemChat.chat.RoomId
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ import androidx.compose.material3.FilledTonalButton
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ import androidx.compose.material3.FilledTonalButton
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|||||||
+1
-1
@@ -30,8 +30,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadPublicChatChannel
|
import com.vitorpamplona.amethyst.ui.note.LoadPublicChatChannel
|
||||||
|
|||||||
+1
-1
@@ -41,7 +41,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip28PublicChat.header
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserIsFollowingChannel
|
||||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ import androidx.compose.material3.FilledTonalButton
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ import androidx.compose.material3.FilledTonalButton
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.njumpLink
|
import com.vitorpamplona.amethyst.ui.note.njumpLink
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery
|
import com.vitorpamplona.amethyst.ui.actions.uploads.SelectSingleFromGallery
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
|||||||
+1
-1
@@ -31,9 +31,9 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.uploads.CompressorQuality
|
import com.vitorpamplona.amethyst.service.uploads.CompressorQuality
|
||||||
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||||
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
|
||||||
|
|||||||
+1
-1
@@ -29,8 +29,8 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadLiveActivityChannel
|
import com.vitorpamplona.amethyst.ui.note.LoadLiveActivityChannel
|
||||||
|
|||||||
+1
-1
@@ -27,8 +27,8 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
import com.vitorpamplona.amethyst.commons.richtext.MediaUrlVideo
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelInfo
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelInfo
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
import com.vitorpamplona.amethyst.ui.components.ZoomableContentView
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.nip53LiveActivities.header
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
|||||||
+1
-1
@@ -40,9 +40,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
|||||||
+1
-1
@@ -36,9 +36,9 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
import com.vitorpamplona.amethyst.ui.note.LikeReaction
|
||||||
|
|||||||
+2
-2
@@ -34,8 +34,8 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
import com.vitorpamplona.amethyst.ui.actions.mediaServers.ServerType
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
|||||||
+9
-9
@@ -33,16 +33,16 @@ import com.vitorpamplona.amethyst.R
|
|||||||
import com.vitorpamplona.amethyst.commons.compose.currentWord
|
import com.vitorpamplona.amethyst.commons.compose.currentWord
|
||||||
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
||||||
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
import com.vitorpamplona.amethyst.commons.compose.replaceCurrentWord
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.Channel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip30CustomEmojis.EmojiPackState
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip53LiveActivities.LiveActivitiesChannel
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip30CustomEmojis.EmojiPackState
|
|
||||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
|
||||||
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
|
||||||
@@ -484,7 +484,7 @@ open class ChannelNewMessageViewModel :
|
|||||||
): List<EmojiUrlTag> {
|
): List<EmojiUrlTag> {
|
||||||
if (myEmojiSet == null) return emptyList()
|
if (myEmojiSet == null) return emptyList()
|
||||||
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
return CustomEmoji.findAllEmojiCodes(message).mapNotNull { possibleEmoji ->
|
||||||
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link.url) }
|
myEmojiSet.firstOrNull { it.code == possibleEmoji }?.let { EmojiUrlTag(it.code, it.link) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -575,11 +575,11 @@ open class ChannelNewMessageViewModel :
|
|||||||
}
|
}
|
||||||
|
|
||||||
open fun autocompleteWithEmojiUrl(item: EmojiPackState.EmojiMedia) {
|
open fun autocompleteWithEmojiUrl(item: EmojiPackState.EmojiMedia) {
|
||||||
val wordToInsert = item.link.url + " "
|
val wordToInsert = item.link + " "
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
iMetaAttachments.downloadAndPrepare(item.link.url) {
|
iMetaAttachments.downloadAndPrepare(item.link) {
|
||||||
Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForImage(item.link.url)
|
Amethyst.instance.roleBasedHttpClientBuilder.okHttpClientForImage(item.link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-1
@@ -104,7 +104,6 @@ fun EditFieldRow(
|
|||||||
it,
|
it,
|
||||||
channelScreenModel::autocompleteWithEmoji,
|
channelScreenModel::autocompleteWithEmoji,
|
||||||
channelScreenModel::autocompleteWithEmojiUrl,
|
channelScreenModel::autocompleteWithEmojiUrl,
|
||||||
accountViewModel,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -41,11 +41,11 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.emphChat.EphemeralChatChannel
|
||||||
|
import com.vitorpamplona.amethyst.commons.model.nip28PublicChats.PublicChatChannel
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo
|
||||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannel
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteHasEvent
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteHasEvent
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserName
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.user.observeUserName
|
||||||
|
|||||||
+2
-2
@@ -31,11 +31,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedContentState
|
||||||
|
import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
import com.vitorpamplona.amethyst.ui.feeds.FeedError
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox
|
||||||
import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user