mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-04-02 08:58:23 +02:00
Converts LiveData for Content-Sensitivity and Transitive Hidden Users into Flow to avoid locking the main thread while scrolling.
Requests flows on the Default thread.
This commit is contained in:
parent
f7c60b3745
commit
a14ab59e78
@ -328,10 +328,10 @@ object LocalPreferences {
|
||||
)
|
||||
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, account.hasDonatedInVersion)
|
||||
|
||||
if (account.showSensitiveContent == null) {
|
||||
if (account.showSensitiveContent.value == null) {
|
||||
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
||||
} else {
|
||||
putBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, account.showSensitiveContent!!)
|
||||
putBoolean(PrefKeys.SHOW_SENSITIVE_CONTENT, account.showSensitiveContent.value!!)
|
||||
}
|
||||
|
||||
putString(
|
||||
@ -597,7 +597,7 @@ object LocalPreferences {
|
||||
backupContactList = latestContactList,
|
||||
proxy = proxy,
|
||||
proxyPort = proxyPort,
|
||||
showSensitiveContent = showSensitiveContent,
|
||||
showSensitiveContent = MutableStateFlow(showSensitiveContent),
|
||||
warnAboutPostsWithReports = warnAboutReports,
|
||||
filterSpamFromStrangers = filterSpam,
|
||||
lastReadPerRoute = lastReadPerRoute,
|
||||
|
@ -26,7 +26,6 @@ import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.switchMap
|
||||
@ -194,7 +193,7 @@ class Account(
|
||||
var backupContactList: ContactListEvent? = null,
|
||||
var proxy: Proxy? = null,
|
||||
var proxyPort: Int = 9050,
|
||||
var showSensitiveContent: Boolean? = null,
|
||||
var showSensitiveContent: MutableStateFlow<Boolean?> = MutableStateFlow(null),
|
||||
var warnAboutPostsWithReports: Boolean = true,
|
||||
var filterSpamFromStrangers: Boolean = true,
|
||||
var lastReadPerRoute: Map<String, Long> = mapOf<String, Long>(),
|
||||
@ -202,7 +201,7 @@ class Account(
|
||||
var pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow<Map<HexKey, String>>(mapOf()),
|
||||
val scope: CoroutineScope = Amethyst.instance.applicationIOScope,
|
||||
) {
|
||||
var transientHiddenUsers: Set<String> = setOf()
|
||||
var transientHiddenUsers: MutableStateFlow<Set<String>> = MutableStateFlow(setOf())
|
||||
|
||||
data class PaymentRequest(
|
||||
val relayUrl: String,
|
||||
@ -238,6 +237,8 @@ class Account(
|
||||
getPrivateOutboxRelayListFlow(),
|
||||
userProfile().flow().relays.stateFlow,
|
||||
) { nip65RelayList, dmRelayList, searchRelayList, privateOutBox, userProfile ->
|
||||
checkNotInMainThread()
|
||||
|
||||
val baseRelaySet = activeRelays() ?: convertLocalRelays()
|
||||
val newDMRelaySet = (dmRelayList.note.event as? ChatMessageRelayListEvent)?.relays()?.map { RelayUrlFormatter.normalize(it) }?.toSet() ?: emptySet()
|
||||
val searchRelaySet = (searchRelayList.note.event as? SearchRelayListEvent)?.relays()?.map { RelayUrlFormatter.normalize(it) }?.toSet() ?: Constants.defaultSearchRelaySet
|
||||
@ -358,6 +359,7 @@ class Account(
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val liveKind3FollowsFlow: Flow<LiveFollowLists> =
|
||||
userProfile().flow().follows.stateFlow.transformLatest {
|
||||
checkNotInMainThread()
|
||||
emit(
|
||||
LiveFollowLists(
|
||||
it.user.cachedFollowingKeySet(),
|
||||
@ -394,6 +396,7 @@ class Account(
|
||||
peopleListFollowsSource: Flow<ListNameNotePair>,
|
||||
): Flow<LiveFollowLists?> =
|
||||
combineTransform(kind3FollowsSource, peopleListFollowsSource) { kind3Follows, peopleListFollows ->
|
||||
checkNotInMainThread()
|
||||
if (peopleListFollows.listName == GLOBAL_FOLLOWS) {
|
||||
emit(null)
|
||||
} else if (peopleListFollows.listName == KIND3_FOLLOWS) {
|
||||
@ -500,10 +503,11 @@ class Account(
|
||||
|
||||
val flowHiddenUsers: StateFlow<LiveHiddenUsers> by lazy {
|
||||
combineTransform(
|
||||
live.asFlow(),
|
||||
transientHiddenUsers,
|
||||
showSensitiveContent,
|
||||
getBlockListNote().flow().metadata.stateFlow,
|
||||
getMuteListNote().flow().metadata.stateFlow,
|
||||
) { localLive, blockList, muteList ->
|
||||
) { transientHiddenUsers, showSensitiveContent, blockList, muteList ->
|
||||
checkNotInMainThread()
|
||||
|
||||
val resultBlockList =
|
||||
@ -532,8 +536,8 @@ class Account(
|
||||
LiveHiddenUsers(
|
||||
hiddenUsers = (resultBlockList.users + resultMuteList.users),
|
||||
hiddenWords = hiddenWords,
|
||||
spammers = localLive.account.transientHiddenUsers,
|
||||
showSensitiveContent = localLive.account.showSensitiveContent,
|
||||
spammers = transientHiddenUsers,
|
||||
showSensitiveContent = showSensitiveContent,
|
||||
),
|
||||
)
|
||||
}.stateIn(
|
||||
@ -542,8 +546,8 @@ class Account(
|
||||
LiveHiddenUsers(
|
||||
hiddenUsers = setOf(),
|
||||
hiddenWords = setOf(),
|
||||
spammers = transientHiddenUsers,
|
||||
showSensitiveContent = showSensitiveContent,
|
||||
spammers = transientHiddenUsers.value,
|
||||
showSensitiveContent = showSensitiveContent.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -596,7 +600,9 @@ class Account(
|
||||
filterSpamFromStrangers = filterSpam
|
||||
LocalCache.antiSpam.active = filterSpamFromStrangers
|
||||
if (!filterSpamFromStrangers) {
|
||||
transientHiddenUsers = setOf()
|
||||
transientHiddenUsers.update {
|
||||
emptySet()
|
||||
}
|
||||
}
|
||||
live.invalidateData()
|
||||
saveable.invalidateData()
|
||||
@ -2377,7 +2383,9 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
transientHiddenUsers = (transientHiddenUsers - pubkeyHex)
|
||||
transientHiddenUsers.update {
|
||||
it - pubkeyHex
|
||||
}
|
||||
live.invalidateData()
|
||||
saveable.invalidateData()
|
||||
}
|
||||
@ -2889,7 +2897,9 @@ class Account(
|
||||
}
|
||||
|
||||
fun updateShowSensitiveContent(show: Boolean?) {
|
||||
showSensitiveContent = show
|
||||
showSensitiveContent.update {
|
||||
show
|
||||
}
|
||||
saveable.invalidateData()
|
||||
live.invalidateData()
|
||||
}
|
||||
@ -2928,10 +2938,12 @@ class Account(
|
||||
// imports transient blocks due to spam.
|
||||
LocalCache.antiSpam.liveSpam.observeForever {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
it.cache.spamMessages.snapshot().values.forEach {
|
||||
if (it.pubkeyHex !in transientHiddenUsers && it.duplicatedMessages.size >= 5) {
|
||||
if (it.pubkeyHex != userProfile().pubkeyHex && it.pubkeyHex !in followingKeySet()) {
|
||||
transientHiddenUsers = (transientHiddenUsers + it.pubkeyHex)
|
||||
it.cache.spamMessages.snapshot().values.forEach { spammer ->
|
||||
if (spammer.pubkeyHex !in transientHiddenUsers.value && spammer.duplicatedMessages.size >= 5) {
|
||||
if (spammer.pubkeyHex != userProfile().pubkeyHex && spammer.pubkeyHex !in followingKeySet()) {
|
||||
transientHiddenUsers.update {
|
||||
it + spammer.pubkeyHex
|
||||
}
|
||||
live.invalidateData()
|
||||
}
|
||||
}
|
||||
|
@ -179,9 +179,7 @@ object Client : RelayPool.Listener {
|
||||
subscriptions = subscriptions.minus(subscriptionId)
|
||||
}
|
||||
|
||||
fun isActive(subscriptionId: String): Boolean {
|
||||
return subscriptions.contains(subscriptionId)
|
||||
}
|
||||
fun isActive(subscriptionId: String): Boolean = subscriptions.contains(subscriptionId)
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun onEvent(
|
||||
@ -205,9 +203,9 @@ object Client : RelayPool.Listener {
|
||||
) {
|
||||
// Releases the Web thread for the new payload.
|
||||
// May need to add a processing queue if processing new events become too costly.
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
listeners.forEach { it.onRelayStateChange(type, relay, channel) }
|
||||
}
|
||||
// GlobalScope.launch(Dispatchers.Default) {
|
||||
listeners.forEach { it.onRelayStateChange(type, relay, channel) }
|
||||
// }
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
@ -249,21 +247,15 @@ object Client : RelayPool.Listener {
|
||||
listeners = listeners.plus(listener)
|
||||
}
|
||||
|
||||
fun isSubscribed(listener: Listener): Boolean {
|
||||
return listeners.contains(listener)
|
||||
}
|
||||
fun isSubscribed(listener: Listener): Boolean = listeners.contains(listener)
|
||||
|
||||
fun unsubscribe(listener: Listener) {
|
||||
listeners = listeners.minus(listener)
|
||||
}
|
||||
|
||||
fun allSubscriptions(): Map<String, List<TypedFilter>> {
|
||||
return subscriptions
|
||||
}
|
||||
fun allSubscriptions(): Map<String, List<TypedFilter>> = subscriptions
|
||||
|
||||
fun getSubscriptionFilters(subId: String): List<TypedFilter> {
|
||||
return subscriptions[subId] ?: emptyList()
|
||||
}
|
||||
fun getSubscriptionFilters(subId: String): List<TypedFilter> = subscriptions[subId] ?: emptyList()
|
||||
|
||||
abstract class Listener {
|
||||
/** A new message was received */
|
||||
|
@ -39,7 +39,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -50,6 +49,7 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled
|
||||
@ -97,10 +97,9 @@ fun SensitivityWarning(
|
||||
accountViewModel: AccountViewModel,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val accountState = accountViewModel.account.showSensitiveContent.collectAsStateWithLifecycle()
|
||||
|
||||
var showContentWarningNote by
|
||||
remember(accountState) { mutableStateOf(accountState?.account?.showSensitiveContent != true) }
|
||||
var showContentWarningNote by remember(accountState) { mutableStateOf(accountState.value != true) }
|
||||
|
||||
CrossfadeIfEnabled(targetState = showContentWarningNote, accountViewModel = accountViewModel) {
|
||||
if (it) {
|
||||
@ -118,18 +117,26 @@ fun ContentWarningNote(onDismiss: () -> Unit) {
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||
Box(
|
||||
Modifier.height(80.dp).width(90.dp),
|
||||
Modifier
|
||||
.height(80.dp)
|
||||
.width(90.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Visibility,
|
||||
contentDescription = stringRes(R.string.content_warning),
|
||||
modifier = Modifier.size(70.dp).align(Alignment.BottomStart),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(70.dp)
|
||||
.align(Alignment.BottomStart),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Warning,
|
||||
contentDescription = stringRes(R.string.content_warning),
|
||||
modifier = Modifier.size(30.dp).align(Alignment.TopEnd),
|
||||
modifier =
|
||||
Modifier
|
||||
.size(30.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
tint = MaterialTheme.colorScheme.onBackground,
|
||||
)
|
||||
}
|
||||
|
@ -26,17 +26,15 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
class HiddenAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
||||
override fun feedKey(): String {
|
||||
return account.userProfile().pubkeyHex
|
||||
}
|
||||
class HiddenAccountsFeedFilter(
|
||||
val account: Account,
|
||||
) : FeedFilter<User>() {
|
||||
override fun feedKey(): String = account.userProfile().pubkeyHex
|
||||
|
||||
override fun showHiddenKey(): Boolean {
|
||||
return true
|
||||
}
|
||||
override fun showHiddenKey(): Boolean = true
|
||||
|
||||
override fun feed(): List<User> {
|
||||
return account.flowHiddenUsers.value.hiddenUsers.reversed().mapNotNull {
|
||||
override fun feed(): List<User> =
|
||||
account.flowHiddenUsers.value.hiddenUsers.reversed().mapNotNull {
|
||||
try {
|
||||
LocalCache.getOrCreateUser(it)
|
||||
} catch (e: Exception) {
|
||||
@ -45,33 +43,26 @@ class HiddenAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HiddenWordsFeedFilter(val account: Account) : FeedFilter<String>() {
|
||||
override fun feedKey(): String {
|
||||
return account.userProfile().pubkeyHex
|
||||
}
|
||||
class HiddenWordsFeedFilter(
|
||||
val account: Account,
|
||||
) : FeedFilter<String>() {
|
||||
override fun feedKey(): String = account.userProfile().pubkeyHex
|
||||
|
||||
override fun showHiddenKey(): Boolean {
|
||||
return true
|
||||
}
|
||||
override fun showHiddenKey(): Boolean = true
|
||||
|
||||
override fun feed(): List<String> {
|
||||
return account.flowHiddenUsers.value.hiddenWords.toList()
|
||||
}
|
||||
override fun feed(): List<String> =
|
||||
account.flowHiddenUsers.value.hiddenWords
|
||||
.toList()
|
||||
}
|
||||
|
||||
class SpammerAccountsFeedFilter(val account: Account) : FeedFilter<User>() {
|
||||
override fun feedKey(): String {
|
||||
return account.userProfile().pubkeyHex
|
||||
}
|
||||
class SpammerAccountsFeedFilter(
|
||||
val account: Account,
|
||||
) : FeedFilter<User>() {
|
||||
override fun feedKey(): String = account.userProfile().pubkeyHex
|
||||
|
||||
override fun showHiddenKey(): Boolean {
|
||||
return true
|
||||
}
|
||||
override fun showHiddenKey(): Boolean = true
|
||||
|
||||
override fun feed(): List<User> {
|
||||
return (account.transientHiddenUsers).map { LocalCache.getOrCreateUser(it) }
|
||||
}
|
||||
override fun feed(): List<User> = account.transientHiddenUsers.value.map { LocalCache.getOrCreateUser(it) }
|
||||
}
|
||||
|
@ -97,6 +97,7 @@ import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrThreadDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
|
||||
import com.vitorpamplona.amethyst.service.NostrVideoDataSource
|
||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||
import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||
@ -758,6 +759,7 @@ class FollowListViewModel(
|
||||
|
||||
private val _kind3GlobalPeopleRoutes =
|
||||
combineTransform(livePeopleListsFlow, liveKind3FollowsFlow) { myLivePeopleListsFlow, myLiveKind3FollowsFlow ->
|
||||
checkNotInMainThread()
|
||||
emit(
|
||||
listOf(listOf(kind3Follow, globalFollow), myLivePeopleListsFlow, myLiveKind3FollowsFlow, listOf(muteListFollow))
|
||||
.flatten()
|
||||
@ -768,6 +770,7 @@ class FollowListViewModel(
|
||||
|
||||
private val _kind3GlobalPeople =
|
||||
combineTransform(livePeopleListsFlow, liveKind3FollowsFlow) { myLivePeopleListsFlow, myLiveKind3FollowsFlow ->
|
||||
checkNotInMainThread()
|
||||
emit(
|
||||
listOf(listOf(kind3Follow, globalFollow), myLivePeopleListsFlow, listOf(muteListFollow))
|
||||
.flatten()
|
||||
|
@ -414,7 +414,7 @@ fun WatchBookmarksFollowsAndAccount(
|
||||
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
||||
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
||||
isSensitive = note.event?.isSensitive() ?: false,
|
||||
showSensitiveContent = showSensitiveContent,
|
||||
showSensitiveContent = showSensitiveContent.value,
|
||||
)
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
|
@ -111,6 +111,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combineTransform
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.joinAll
|
||||
@ -308,22 +309,19 @@ class AccountViewModel(
|
||||
noteIsHiddenFlows.get(note)
|
||||
?: combineTransform(
|
||||
account.flowHiddenUsers,
|
||||
account.liveKind3FollowsFlow,
|
||||
account.liveKind3Follows,
|
||||
note.flow().metadata.stateFlow,
|
||||
note.flow().reports.stateFlow,
|
||||
) { hiddenUsers, followingUsers, metadata, reports ->
|
||||
val isAcceptable =
|
||||
withContext(Dispatchers.IO) {
|
||||
isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.users)
|
||||
}
|
||||
emit(isAcceptable)
|
||||
}.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.Eagerly,
|
||||
NoteComposeReportState(),
|
||||
).also {
|
||||
noteIsHiddenFlows.put(note, it)
|
||||
}
|
||||
emit(isNoteAcceptable(metadata.note, hiddenUsers, followingUsers.users))
|
||||
}.flowOn(Dispatchers.Default)
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.Eagerly,
|
||||
NoteComposeReportState(),
|
||||
).also {
|
||||
noteIsHiddenFlows.put(note, it)
|
||||
}
|
||||
|
||||
private val noteMustShowExpandButtonFlows = LruCache<Note, StateFlow<Boolean>>(300)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user