Refactor notifications to Kotlin & paging (#4026)

This refactors the NotificationsFragment and related classes to Kotlin &
paging.
While trying to preserve as much of the original behavior as possible,
this adds the following improvements as well:
- The "show notifications filter" preference was added again
- The "load more" button now has a background ripple effect when clicked
- The "legal" report category of Mastodon 4.2 is now supported in report
notifications
- Unknown notifications now display "unknown notification type" instead
of an empty line

Other code quality improvements:
- All views from xml layouts are now referenced via ViewBindings
- the classes responsible for showing system notifications were moved to
a new package `systemnotifications` while the classes from this
refactoring are in `notifications`
- the id of the local Tusky account is now called `tuskyAccountId` in
all places I could find

closes https://github.com/tuskyapp/Tusky/issues/3429

---------

Co-authored-by: Zongle Wang <wangzongler@gmail.com>
This commit is contained in:
Konrad Pozniak 2024-05-03 18:27:10 +02:00 committed by GitHub
commit b2c0b18c8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
121 changed files with 6992 additions and 4654 deletions

View file

@ -2,6 +2,9 @@ package com.keylesspalace.tusky.appstore
import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.db.AppDatabase
import com.keylesspalace.tusky.entity.Poll
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -9,40 +12,64 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
/**
* Updates the database cache in response to events.
* This is important for the home timeline and notifications to be up to date.
*/
@OptIn(ExperimentalStdlibApi::class)
class CacheUpdater @Inject constructor(
eventHub: EventHub,
accountManager: AccountManager,
appDatabase: AppDatabase
appDatabase: AppDatabase,
moshi: Moshi
) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
init {
val timelineDao = appDatabase.timelineDao()
private val timelineDao = appDatabase.timelineDao()
private val statusDao = appDatabase.timelineStatusDao()
private val notificationsDao = appDatabase.notificationsDao()
init {
scope.launch {
eventHub.events.collect { event ->
val accountId = accountManager.activeAccount?.id ?: return@collect
val tuskyAccountId = accountManager.activeAccount?.id ?: return@collect
when (event) {
is StatusChangedEvent -> {
val status = event.status
timelineDao.update(
accountId = accountId,
status = status
)
is StatusChangedEvent -> statusDao.update(
tuskyAccountId = tuskyAccountId,
status = event.status,
moshi = moshi
)
is UnfollowEvent -> timelineDao.removeStatusesAndReblogsByUser(tuskyAccountId, event.accountId)
is BlockEvent -> removeAllByUser(tuskyAccountId, event.accountId)
is MuteEvent -> removeAllByUser(tuskyAccountId, event.accountId)
is DomainMuteEvent -> {
timelineDao.deleteAllFromInstance(tuskyAccountId, event.instance)
notificationsDao.deleteAllFromInstance(tuskyAccountId, event.instance)
}
is UnfollowEvent ->
timelineDao.removeAllByUser(accountId, event.accountId)
is StatusDeletedEvent ->
timelineDao.delete(accountId, event.statusId)
is StatusDeletedEvent -> {
timelineDao.deleteAllWithStatus(tuskyAccountId, event.statusId)
notificationsDao.deleteAllWithStatus(tuskyAccountId, event.statusId)
}
is PollVoteEvent -> {
timelineDao.setVoted(accountId, event.statusId, event.poll)
val pollString = moshi.adapter<Poll>().toJson(event.poll)
statusDao.setVoted(tuskyAccountId, event.statusId, pollString)
}
}
}
}
}
private suspend fun removeAllByUser(tuskyAccountId: Long, accountId: String) {
timelineDao.removeAllByUser(tuskyAccountId, accountId)
notificationsDao.removeAllByUser(tuskyAccountId, accountId)
}
fun stop() {
this.scope.cancel()
}

View file

@ -1,14 +1,10 @@
package com.keylesspalace.tusky.appstore
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import java.util.function.Consumer
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
interface Event
@ -21,13 +17,4 @@ class EventHub @Inject constructor() {
suspend fun dispatch(event: Event) {
_events.emit(event)
}
// TODO remove as soon as NotificationsFragment is Kotlin
fun subscribe(lifecycleOwner: LifecycleOwner, consumer: Consumer<Event>) {
lifecycleOwner.lifecycleScope.launch {
events.collect { event ->
consumer.accept(event)
}
}
}
}