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:
parent
3bbf96b057
commit
b2c0b18c8e
121 changed files with 6992 additions and 4654 deletions
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023 Tusky Contributors
|
||||
*
|
||||
* This file is a part of Tusky.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
||||
* see <http://www.gnu.org/licenses>.
|
||||
*/
|
||||
|
||||
package com.keylesspalace.tusky.util
|
||||
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.TimeMark
|
||||
import kotlin.time.TimeSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/**
|
||||
* Returns a flow that mirrors the original flow, but filters out values that occur within
|
||||
* [timeout] of the previously emitted value. The first value is always emitted.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```kotlin
|
||||
* flow {
|
||||
* emit(1)
|
||||
* delay(90.milliseconds)
|
||||
* emit(2)
|
||||
* delay(90.milliseconds)
|
||||
* emit(3)
|
||||
* delay(1010.milliseconds)
|
||||
* emit(4)
|
||||
* delay(1010.milliseconds)
|
||||
* emit(5)
|
||||
* }.throttleFirst(1000.milliseconds)
|
||||
* ```
|
||||
*
|
||||
* produces the following emissions.
|
||||
*
|
||||
* ```text
|
||||
* 1, 4, 5
|
||||
* ```
|
||||
*
|
||||
* @see kotlinx.coroutines.flow.debounce(Duration)
|
||||
* @param timeout Emissions within this duration of the last emission are filtered
|
||||
* @param timeSource Used to measure elapsed time. Normally only overridden in tests
|
||||
*/
|
||||
fun <T> Flow<T>.throttleFirst(timeout: Duration, timeSource: TimeSource = TimeSource.Monotonic) =
|
||||
flow {
|
||||
var marker: TimeMark? = null
|
||||
collect {
|
||||
if (marker == null || marker!!.elapsedNow() >= timeout) {
|
||||
emit(it)
|
||||
marker = timeSource.markNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,13 +20,6 @@ package com.keylesspalace.tusky.util
|
|||
import java.util.ArrayList
|
||||
import java.util.LinkedHashSet
|
||||
|
||||
/**
|
||||
* @return true if list is null or else return list.isEmpty()
|
||||
*/
|
||||
fun isEmpty(list: List<*>?): Boolean {
|
||||
return list == null || list.isEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new ArrayList containing the elements without duplicates in the same order
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package com.keylesspalace.tusky.util
|
|||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity
|
||||
import java.util.Locale
|
||||
|
||||
private const val TAG: String = "LocaleUtils"
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
package com.keylesspalace.tusky.util
|
||||
|
||||
import androidx.arch.core.util.Function
|
||||
|
||||
/**
|
||||
* This list implementation can help to keep two lists in sync - like real models and view models.
|
||||
*
|
||||
* Every operation on the main list triggers update of the supplementary list (but not vice versa).
|
||||
*
|
||||
* This makes sure that the main list is always the source of truth.
|
||||
*
|
||||
* Main list is projected to the supplementary list by the passed mapper function.
|
||||
*
|
||||
* Paired list is newer actually exposed and clients are provided with `getPairedCopy()`,
|
||||
* `getPairedItem()` and `setPairedItem()`. This prevents modifications of the
|
||||
* supplementary list size so lists are always have the same length.
|
||||
*
|
||||
* This implementation will not try to recover from exceptional cases so lists may be out of sync
|
||||
* after the exception.
|
||||
*
|
||||
* It is most useful with immutable data because we cannot track changes inside stored objects.
|
||||
*
|
||||
* @param T type of elements in the main list
|
||||
* @param V type of elements in supplementary list
|
||||
* @param mapper Function, which will be used to translate items from the main list to the
|
||||
* supplementary one.
|
||||
* @constructor
|
||||
*/
|
||||
class PairedList<T, V>(private val mapper: Function<T, out V>) : AbstractMutableList<T>() {
|
||||
private val main: MutableList<T> = ArrayList()
|
||||
private val synced: MutableList<V> = ArrayList()
|
||||
|
||||
val pairedCopy: List<V>
|
||||
get() = ArrayList(synced)
|
||||
|
||||
fun getPairedItem(index: Int): V {
|
||||
return synced[index]
|
||||
}
|
||||
|
||||
fun getPairedItemOrNull(index: Int): V? {
|
||||
return synced.getOrNull(index)
|
||||
}
|
||||
|
||||
fun setPairedItem(index: Int, element: V) {
|
||||
synced[index] = element
|
||||
}
|
||||
|
||||
override fun get(index: Int): T {
|
||||
return main[index]
|
||||
}
|
||||
|
||||
override fun set(index: Int, element: T): T {
|
||||
synced[index] = mapper.apply(element)
|
||||
return main.set(index, element)
|
||||
}
|
||||
|
||||
override fun add(element: T): Boolean {
|
||||
synced.add(mapper.apply(element))
|
||||
return main.add(element)
|
||||
}
|
||||
|
||||
override fun add(index: Int, element: T) {
|
||||
synced.add(index, mapper.apply(element))
|
||||
main.add(index, element)
|
||||
}
|
||||
|
||||
override fun removeAt(index: Int): T {
|
||||
synced.removeAt(index)
|
||||
return main.removeAt(index)
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = main.size
|
||||
}
|
||||
|
|
@ -28,8 +28,8 @@ import androidx.core.graphics.drawable.IconCompat
|
|||
import com.bumptech.glide.Glide
|
||||
import com.keylesspalace.tusky.MainActivity
|
||||
import com.keylesspalace.tusky.R
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity
|
||||
import com.keylesspalace.tusky.di.ApplicationScope
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
package com.keylesspalace.tusky.util
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||
import at.connyduck.calladapter.networkresult.fold
|
||||
import java.util.function.Consumer
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Simple reimplementation of RxJava's Single using a Kotlin coroutine,
|
||||
* intended to be consumed by legacy Java code only.
|
||||
*/
|
||||
class Single<T>(private val producer: suspend CoroutineScope.() -> NetworkResult<T>) {
|
||||
fun subscribe(
|
||||
owner: LifecycleOwner,
|
||||
onSuccess: Consumer<T>,
|
||||
onError: Consumer<Throwable>
|
||||
): Job {
|
||||
return owner.lifecycleScope.launch {
|
||||
producer().fold(
|
||||
onSuccess = { onSuccess.accept(it) },
|
||||
onFailure = { onError.accept(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
package com.keylesspalace.tusky.util
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import com.keylesspalace.tusky.db.AccountEntity
|
||||
import com.keylesspalace.tusky.db.entity.AccountEntity
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
|
||||
data class StatusDisplayOptions(
|
||||
|
|
|
|||
|
|
@ -36,10 +36,8 @@ package com.keylesspalace.tusky.util
|
|||
|
||||
import androidx.paging.CombinedLoadStates
|
||||
import androidx.paging.LoadState
|
||||
import com.keylesspalace.tusky.entity.Notification
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.entity.TrendingTag
|
||||
import com.keylesspalace.tusky.viewdata.NotificationViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import com.keylesspalace.tusky.viewdata.TranslationViewData
|
||||
import com.keylesspalace.tusky.viewdata.TrendingViewData
|
||||
|
|
@ -61,21 +59,6 @@ fun Status.toViewData(
|
|||
)
|
||||
}
|
||||
|
||||
@JvmName("notificationToViewData")
|
||||
fun Notification.toViewData(
|
||||
isShowingContent: Boolean,
|
||||
isExpanded: Boolean,
|
||||
isCollapsed: Boolean
|
||||
): NotificationViewData.Concrete {
|
||||
return NotificationViewData.Concrete(
|
||||
this.type,
|
||||
this.id,
|
||||
this.account,
|
||||
this.status?.toViewData(isShowingContent, isExpanded, isCollapsed),
|
||||
this.report
|
||||
)
|
||||
}
|
||||
|
||||
fun List<TrendingTag>.toViewData(): List<TrendingViewData.Tag> {
|
||||
val maxTrendingValue = flatMap { tag -> tag.history }
|
||||
.mapNotNull { it.uses.toLongOrNull() }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue