Merge branch 'main' into main

This commit is contained in:
Rashed
2023-03-01 09:16:59 +02:00
committed by GitHub
26 changed files with 97 additions and 146 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId "com.vitorpamplona.amethyst" applicationId "com.vitorpamplona.amethyst"
minSdk 26 minSdk 26
targetSdk 33 targetSdk 33
versionCode 77 versionCode 79
versionName "0.20.3" versionName "0.20.5"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@@ -44,12 +44,10 @@ class NotificationLiveData(val cache: NotificationCache): LiveData<NotificationS
// Refreshes observers in batches. // Refreshes observers in batches.
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() { fun invalidateData() {
if (!hasActiveObservers()) return if (!hasActiveObservers()) return
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch { scope.launch {
try { try {
@@ -33,7 +33,6 @@ object ServiceManager {
NostrAccountDataSource.account = myAccount NostrAccountDataSource.account = myAccount
NostrHomeDataSource.account = myAccount NostrHomeDataSource.account = myAccount
NostrChatroomListDataSource.account = myAccount NostrChatroomListDataSource.account = myAccount
NostrGlobalDataSource.account = myAccount
// Notification Elements // Notification Elements
NostrAccountDataSource.start() NostrAccountDataSource.start()
@@ -605,11 +605,9 @@ class Account(
class AccountLiveData(private val account: Account): LiveData<AccountState>(AccountState(account)) { class AccountLiveData(private val account: Account): LiveData<AccountState>(AccountState(account)) {
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Default) val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch { scope.launch {
try { try {
@@ -67,12 +67,10 @@ class AntiSpamLiveData(val cache: AntiSpamFilter): LiveData<AntiSpamState>(AntiS
// Refreshes observers in batches. // Refreshes observers in batches.
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() { fun invalidateData() {
if (!hasActiveObservers()) return if (!hasActiveObservers()) return
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch { scope.launch {
try { try {
@@ -23,7 +23,6 @@ class Channel(val idHex: String) {
return info.name ?: idDisplayNote() return info.name ?: idDisplayNote()
} }
@Synchronized
fun addNote(note: Note) { fun addNote(note: Note) {
notes[note.idHex] = note notes[note.idHex] = note
} }
@@ -408,8 +408,8 @@ object LocalCache {
// Already processed this event. // Already processed this event.
if (note.event != null) return if (note.event != null) return
val mentions = event.reportedAuthor.mapNotNull { checkGetOrCreateUser(it) } val mentions = event.reportedAuthor.mapNotNull { checkGetOrCreateUser(it.key) }
val repliesTo = event.reportedPost.mapNotNull { checkGetOrCreateNote(it) } val repliesTo = event.reportedPost.mapNotNull { checkGetOrCreateNote(it.key) }
note.loadEvent(event, author, mentions, repliesTo) note.loadEvent(event, author, mentions, repliesTo)
@@ -735,12 +735,10 @@ class LocalCacheLiveData(val cache: LocalCache): LiveData<LocalCacheState>(Local
// Refreshes observers in batches. // Refreshes observers in batches.
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() { fun invalidateData() {
if (!hasActiveObservers()) return if (!hasActiveObservers()) return
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch { scope.launch {
try { try {
@@ -18,8 +18,10 @@ import java.util.regex.Pattern
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nostr.postr.events.Event import nostr.postr.events.Event
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]") val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
@@ -318,17 +320,20 @@ class NoteLiveData(val note: Note): LiveData<NoteState>(NoteState(note)) {
// Refreshes observers in batches. // Refreshes observers in batches.
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() { fun invalidateData() {
if (!hasActiveObservers()) return if (!hasActiveObservers()) return
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch { scope.launch {
delay(100) try {
refresh() delay(100)
handlerWaiting.set(false) refresh()
} finally {
withContext(NonCancellable) {
handlerWaiting.set(false)
}
}
} }
} }
@@ -289,7 +289,7 @@ class User(val pubkeyHex: String) {
fun hasReport(loggedIn: User, type: ReportEvent.ReportType): Boolean { fun hasReport(loggedIn: User, type: ReportEvent.ReportType): Boolean {
return reports[loggedIn]?.firstOrNull() { return reports[loggedIn]?.firstOrNull() {
it.event is ReportEvent && (it.event as ReportEvent).reportType.contains(type) it.event is ReportEvent && (it.event as ReportEvent).reportedAuthor.any { it.reportType == type }
} != null } != null
} }
@@ -379,11 +379,9 @@ class UserLiveData(val user: User): LiveData<UserState>(UserState(user)) {
// Refreshes observers in batches. // Refreshes observers in batches.
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Main) val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch { scope.launch {
try { try {
@@ -17,11 +17,14 @@ import com.vitorpamplona.amethyst.service.relays.Subscription
import com.vitorpamplona.amethyst.service.relays.hasValidSignature import com.vitorpamplona.amethyst.service.relays.hasValidSignature
import java.util.Date import java.util.Date
import java.util.UUID import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nostr.postr.events.ContactListEvent import nostr.postr.events.ContactListEvent
import nostr.postr.events.DeletionEvent import nostr.postr.events.DeletionEvent
import nostr.postr.events.Event import nostr.postr.events.Event
@@ -42,6 +45,7 @@ abstract class NostrDataSource(val debugName: String) {
} }
} }
private val clientListener = object : Client.Listener() { private val clientListener = object : Client.Listener() {
override fun onEvent(event: Event, subscriptionId: String, relay: Relay) { override fun onEvent(event: Event, subscriptionId: String, relay: Relay) {
if (subscriptionId in subscriptions.keys) { if (subscriptionId in subscriptions.keys) {
@@ -91,7 +95,6 @@ abstract class NostrDataSource(val debugName: String) {
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }
@@ -123,10 +126,12 @@ abstract class NostrDataSource(val debugName: String) {
} }
open fun start() { open fun start() {
println("DataSource: ${this.javaClass.simpleName} Start")
resetFilters() resetFilters()
} }
open fun stop() { open fun stop() {
println("DataSource: ${this.javaClass.simpleName} Stop")
subscriptions.values.forEach { channel -> subscriptions.values.forEach { channel ->
Client.close(channel.id) Client.close(channel.id)
channel.typedFilters = null channel.typedFilters = null
@@ -144,17 +149,24 @@ abstract class NostrDataSource(val debugName: String) {
subscriptions = subscriptions.minus(subscription.id) subscriptions = subscriptions.minus(subscription.id)
} }
var handlerWaiting = false // Refreshes observers in batches.
@Synchronized var handlerWaiting = AtomicBoolean()
fun invalidateFilters() {
if (handlerWaiting) return fun invalidateFilters() {
if (handlerWaiting.getAndSet(true)) return
println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
handlerWaiting = true
val scope = CoroutineScope(Job() + Dispatchers.IO) val scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch { scope.launch {
delay(200) try {
resetFiltersSuspend() delay(200)
handlerWaiting = false resetFiltersSuspend()
} finally {
withContext(NonCancellable) {
handlerWaiting.set(false)
}
}
} }
} }
@@ -8,7 +8,6 @@ import nostr.postr.JsonFilter
import nostr.postr.events.TextNoteEvent import nostr.postr.events.TextNoteEvent
object NostrGlobalDataSource: NostrDataSource("GlobalFeed") { object NostrGlobalDataSource: NostrDataSource("GlobalFeed") {
lateinit var account: Account
fun createGlobalFilter() = TypedFilter( fun createGlobalFilter() = TypedFilter(
types = setOf(FeedType.GLOBAL), types = setOf(FeedType.GLOBAL),
filter = JsonFilter( filter = JsonFilter(
@@ -5,6 +5,8 @@ import nostr.postr.Utils
import nostr.postr.events.Event import nostr.postr.events.Event
import nostr.postr.toHex import nostr.postr.toHex
data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType)
// NIP 56 event. // NIP 56 event.
class ReportEvent ( class ReportEvent (
id: ByteArray, id: ByteArray,
@@ -15,14 +17,37 @@ class ReportEvent (
sig: ByteArray sig: ByteArray
): Event(id, pubKey, createdAt, kind, tags, content, sig) { ): Event(id, pubKey, createdAt, kind, tags, content, sig) {
@Transient val reportType: List<ReportType> @Transient val reportedPost: List<ReportedKey>
@Transient val reportedPost: List<String> @Transient val reportedAuthor: List<ReportedKey>
@Transient val reportedAuthor: List<String>
init { init {
reportType = tags.filter { it.firstOrNull() == "report" }.mapNotNull { it.getOrNull(1) }.map { ReportType.valueOf(it.toUpperCase()) } // Works with old and new structures for report.
reportedPost = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
reportedAuthor = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) } var reportType = tags.filter { it.firstOrNull() == "report" }.mapNotNull { it.getOrNull(1) }.map { ReportType.valueOf(it.toUpperCase()) }.firstOrNull()
if (reportType == null) {
reportType = tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.toUpperCase()) }.firstOrNull()
}
if (reportType == null) {
reportType = ReportType.SPAM
}
reportedPost = tags
.filter { it.firstOrNull() == "e" && it.getOrNull(1) != null }
.map {
ReportedKey(
it[1],
it.getOrNull(2)?.toUpperCase()?.let { it1 -> ReportType.valueOf(it1) }?: reportType
)
}
reportedAuthor = tags
.filter { it.firstOrNull() == "p" && it.getOrNull(1) != null }
.map {
ReportedKey(
it[1],
it.getOrNull(2)?.toUpperCase()?.let { it1 -> ReportType.valueOf(it1) }?: reportType
)
}
} }
companion object { companion object {
@@ -31,12 +56,11 @@ class ReportEvent (
fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent { fun create(reportedPost: Event, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
val content = "" val content = ""
val reportTypeTag = listOf("report", type.name.toLowerCase()) val reportPostTag = listOf("e", reportedPost.id.toHex(), type.name.toLowerCase())
val reportPostTag = listOf("e", reportedPost.id.toHex()) val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex(), type.name.toLowerCase())
val reportAuthorTag = listOf("p", reportedPost.pubKey.toHex())
val pubKey = Utils.pubkeyCreate(privateKey) val pubKey = Utils.pubkeyCreate(privateKey)
val tags:List<List<String>> = listOf(reportTypeTag, reportPostTag, reportAuthorTag) val tags:List<List<String>> = listOf(reportPostTag, reportAuthorTag)
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
return ReportEvent(id, pubKey, createdAt, tags, content, sig) return ReportEvent(id, pubKey, createdAt, tags, content, sig)
@@ -45,11 +69,10 @@ class ReportEvent (
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent { fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
val content = "" val content = ""
val reportTypeTag = listOf("report", type.name.toLowerCase()) val reportAuthorTag = listOf("p", reportedUser, type.name.toLowerCase())
val reportAuthorTag = listOf("p", reportedUser)
val pubKey = Utils.pubkeyCreate(privateKey) val pubKey = Utils.pubkeyCreate(privateKey)
val tags:List<List<String>> = listOf(reportTypeTag, reportAuthorTag) val tags:List<List<String>> = listOf(reportAuthorTag)
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
return ReportEvent(id, pubKey, createdAt, tags, content, sig) return ReportEvent(id, pubKey, createdAt, tags, content, sig)
@@ -57,11 +80,11 @@ class ReportEvent (
} }
enum class ReportType() { enum class ReportType() {
EXPLICIT, EXPLICIT, // Not used anymore.
ILLEGAL, ILLEGAL,
SPAM, SPAM,
IMPERSONATION, IMPERSONATION,
NUDITY, NUDITY,
PROFANITY PROFANITY,
} }
} }
@@ -94,7 +94,6 @@ object RelayPool: Relay.Listener {
fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay)
} }
@Synchronized
override fun onEvent(relay: Relay, subscriptionId: String, event: Event) { override fun onEvent(relay: Relay, subscriptionId: String, event: Event) {
listeners.forEach { it.onEvent(event, subscriptionId, relay) } listeners.forEach { it.onEvent(event, subscriptionId, relay) }
} }
@@ -23,19 +23,6 @@ object NotificationFeedFilter: FeedFilter<Note>() {
&& it.event !is ChannelMetadataEvent && it.event !is ChannelMetadataEvent
&& it.event !is LnZapRequestEvent && it.event !is LnZapRequestEvent
} }
.filter {
it.event !is TextNoteEvent
||
(
it.event is TextNoteEvent
&&
(
it.replyTo?.lastOrNull()?.author == account.userProfile()
||
account.userProfile() in it.directlyCiteUsers()
)
)
}
.filter { .filter {
it.event !is ReactionEvent it.event !is ReactionEvent
|| ||
@@ -193,7 +193,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, r
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Box(modifier = Modifier Box(modifier = Modifier
.width(55.dp) .width(65.dp)
.padding(0.dp)) { .padding(0.dp)) {
} }
@@ -94,7 +94,8 @@ fun ObserveDisplayNip05Status(baseUser: User) {
user.nip05()?.let { nip05 -> user.nip05()?.let { nip05 ->
if (nip05.split("@").size == 2) { if (nip05.split("@").size == 2) {
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (nip05.split("@")[0] != "_") if (nip05.split("@")[0] != "_")
Text( Text(
@@ -46,6 +46,7 @@ import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Following import com.vitorpamplona.amethyst.ui.theme.Following
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
import nostr.postr.events.PrivateDmEvent
import nostr.postr.events.TextNoteEvent import nostr.postr.events.TextNoteEvent
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@@ -319,25 +320,26 @@ fun NoteCompose(
val reportType = noteEvent.reportType.map { val reportType = noteEvent.reportType.map {
when (it) { when (it) {
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content) ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
ReportEvent.ReportType.SPAM -> stringResource(R.string.spam) ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation) ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior) ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
ReportEvent.ReportType.PROFANITY -> stringResource(R.string.profanity_hateful_speech)
else -> stringResource(R.string.unknown) else -> stringResource(R.string.unknown)
} }
}.joinToString(", ") }.toSet().joinToString(", ")
Text( Text(
text = reportType text = reportType
) )
Divider( Divider(
modifier = Modifier.padding(top = 10.dp), modifier = Modifier.padding(top = 40.dp),
thickness = 0.25.dp thickness = 0.25.dp
) )
} else { } else {
val eventContent = noteEvent.content val eventContent = accountViewModel.decrypt(note)
val canPreview = note.author == account.userProfile() val canPreview = note.author == account.userProfile()
|| (note.author?.let { account.userProfile().isFollowing(it) } ?: true ) || (note.author?.let { account.userProfile().isFollowing(it) } ?: true )
|| !noteForReports.hasAnyReports() || !noteForReports.hasAnyReports()
@@ -605,6 +607,13 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
}) { }) {
Text(stringResource(R.string.report_spam_scam)) Text(stringResource(R.string.report_spam_scam))
} }
DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.PROFANITY);
note.author?.let { accountViewModel.hide(it, context) }
onDismiss()
}) {
Text("Report Hateful Speech")
}
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION); accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION);
note.author?.let { accountViewModel.hide(it, context) } note.author?.let { accountViewModel.hide(it, context) }
@@ -613,11 +622,11 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
Text(stringResource(R.string.report_impersonation)) Text(stringResource(R.string.report_impersonation))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.EXPLICIT); accountViewModel.report(note, ReportEvent.ReportType.NUDITY);
note.author?.let { accountViewModel.hide(it, context) } note.author?.let { accountViewModel.hide(it, context) }
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_explicit_content)) Text(stringResource(R.string.report_nudity_porn))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL); accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL);
@@ -129,11 +129,9 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>): ViewModel() {
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
private fun invalidateData() { private fun invalidateData() {
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Default) val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch { scope.launch {
try { try {
@@ -87,11 +87,10 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>): ViewModel() {
} }
private var handlerWaiting = AtomicBoolean() private var handlerWaiting = AtomicBoolean()
@Synchronized
fun invalidateData() { fun invalidateData() {
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Default) val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch { scope.launch {
try { try {
@@ -61,11 +61,9 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>): Vie
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
private fun invalidateData() { private fun invalidateData() {
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Default) val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch { scope.launch {
try { try {
@@ -83,11 +83,9 @@ class RelayFeedViewModel: ViewModel() {
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
private fun invalidateData() { private fun invalidateData() {
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Default) val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch { scope.launch {
try { try {
@@ -65,11 +65,9 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>): ViewModel() {
var handlerWaiting = AtomicBoolean() var handlerWaiting = AtomicBoolean()
@Synchronized
private fun invalidateData() { private fun invalidateData() {
if (handlerWaiting.getAndSet(true)) return if (handlerWaiting.getAndSet(true)) return
handlerWaiting.set(true)
val scope = CoroutineScope(Job() + Dispatchers.Default) val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch { scope.launch {
try { try {
@@ -89,31 +89,11 @@ fun TabKnown(accountViewModel: AccountViewModel, navController: NavController) {
ChatroomListKnownFeedFilter.account = account ChatroomListKnownFeedFilter.account = account
val feedViewModel: NostrChatroomListKnownFeedViewModel = viewModel() val feedViewModel: NostrChatroomListKnownFeedViewModel = viewModel()
LaunchedEffect(Unit) { LaunchedEffect(accountViewModel) {
NostrChatroomListDataSource.resetFilters() NostrChatroomListDataSource.resetFilters()
feedViewModel.invalidateData() feedViewModel.invalidateData()
} }
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Chatroom List Start")
NostrChatroomListDataSource.start()
feedViewModel.invalidateData()
}
if (event == Lifecycle.Event.ON_PAUSE) {
println("Chatroom List Stop")
NostrChatroomListDataSource.stop()
}
}
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
}
}
Column(Modifier.fillMaxHeight()) { Column(Modifier.fillMaxHeight()) {
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
@@ -131,29 +111,9 @@ fun TabNew(accountViewModel: AccountViewModel, navController: NavController) {
ChatroomListNewFeedFilter.account = account ChatroomListNewFeedFilter.account = account
val feedViewModel: NostrChatroomListNewFeedViewModel = viewModel() val feedViewModel: NostrChatroomListNewFeedViewModel = viewModel()
LaunchedEffect(Unit) { LaunchedEffect(accountViewModel) {
NostrChatroomListDataSource.resetFilters() NostrChatroomListDataSource.resetFilters()
feedViewModel.refresh() // refresh view feedViewModel.invalidateData() // refresh view
}
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Chatroom List Start")
NostrChatroomListDataSource.start()
feedViewModel.invalidateData()
}
if (event == Lifecycle.Event.ON_PAUSE) {
println("Chatroom List Stop")
NostrChatroomListDataSource.stop()
}
}
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
}
} }
Column(Modifier.fillMaxHeight()) { Column(Modifier.fillMaxHeight()) {
@@ -58,27 +58,6 @@ fun HomeScreen(accountViewModel: AccountViewModel, navController: NavController)
feedViewModelReplies.invalidateData() feedViewModelReplies.invalidateData()
} }
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Home Start")
NostrHomeDataSource.start()
feedViewModel.invalidateData()
feedViewModelReplies.invalidateData()
}
if (event == Lifecycle.Event.ON_PAUSE) {
println("Home Stop")
NostrHomeDataSource.stop()
}
}
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
}
}
Column(Modifier.fillMaxHeight()) { Column(Modifier.fillMaxHeight()) {
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
@@ -836,7 +836,6 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
}) { }) {
Text(stringResource(R.string.report_hateful_speech)) Text(stringResource(R.string.report_hateful_speech))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION); accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION);
user.let { accountViewModel.hide(it, context) } user.let { accountViewModel.hide(it, context) }
@@ -83,7 +83,6 @@ fun SearchScreen(accountViewModel: AccountViewModel, navController: NavControlle
val account = accountState?.account ?: return val account = accountState?.account ?: return
GlobalFeedFilter.account = account GlobalFeedFilter.account = account
NostrGlobalDataSource.account = account
val feedViewModel: NostrGlobalFeedViewModel = viewModel() val feedViewModel: NostrGlobalFeedViewModel = viewModel()
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current