only check once for filters v2 availability (#4539)
Instead of calling the endpoint every time filters are needed, it will be called only once and the result cached. This will result in quite some requests less on instances supporting v2. I also tested v1 filters and made some small improvements. We should [remove filters v1 support](https://github.com/tuskyapp/Tusky/issues/4538) some time in the future though.
This commit is contained in:
parent
859ffd121e
commit
8a57bcc3f4
File diff suppressed because it is too large
Load Diff
|
@ -212,7 +212,7 @@ class StatusListActivity : BottomSheetActivity() {
|
|||
mastodonApi.getFiltersV1().fold(
|
||||
{ filters ->
|
||||
mutedFilterV1 = filters.firstOrNull { filter ->
|
||||
hashedTag == filter.phrase && filter.context.contains(FilterV1.HOME)
|
||||
hashedTag == filter.phrase && filter.context.contains(Filter.Kind.HOME.kind)
|
||||
}
|
||||
updateTagMuteState(mutedFilterV1 != null)
|
||||
},
|
||||
|
@ -249,7 +249,7 @@ class StatusListActivity : BottomSheetActivity() {
|
|||
|
||||
mastodonApi.createFilter(
|
||||
title = "#$tag",
|
||||
context = listOf(FilterV1.HOME),
|
||||
context = listOf(Filter.Kind.HOME.kind),
|
||||
filterAction = Filter.Action.WARN.action,
|
||||
expiresInSeconds = null
|
||||
).fold(
|
||||
|
@ -278,7 +278,7 @@ class StatusListActivity : BottomSheetActivity() {
|
|||
if (throwable.isHttpNotFound()) {
|
||||
mastodonApi.createFilterV1(
|
||||
hashedTag,
|
||||
listOf(FilterV1.HOME),
|
||||
listOf(Filter.Kind.HOME.kind),
|
||||
irreversible = false,
|
||||
wholeWord = true,
|
||||
expiresInSeconds = null
|
||||
|
@ -355,7 +355,7 @@ class StatusListActivity : BottomSheetActivity() {
|
|||
mastodonApi.updateFilterV1(
|
||||
id = filter.id,
|
||||
phrase = filter.phrase,
|
||||
context = filter.context.filter { it != FilterV1.HOME },
|
||||
context = filter.context.filter { it != Filter.Kind.HOME.kind },
|
||||
irreversible = null,
|
||||
wholeWord = null,
|
||||
expiresInSeconds = null
|
||||
|
|
|
@ -107,6 +107,10 @@ class InstanceInfoRepository @Inject constructor(
|
|||
}
|
||||
}.toInfoOrDefault()
|
||||
|
||||
suspend fun saveFilterV2Support(filterV2Supported: Boolean) = dao.setFilterV2Support(instanceName, filterV2Supported)
|
||||
|
||||
suspend fun isFilterV2Supported(): Boolean = dao.getFilterV2Support(instanceName)
|
||||
|
||||
private suspend fun InstanceInfoRepository.fetchAndPersistInstanceInfo(): NetworkResult<InstanceInfoEntity> =
|
||||
fetchRemoteInstanceInfo()
|
||||
.onSuccess { instanceInfoEntity ->
|
||||
|
|
|
@ -127,11 +127,17 @@ class NotificationsViewModel @Inject constructor(
|
|||
onPreferenceChanged(event.preferenceKey)
|
||||
}
|
||||
if (event is FilterUpdatedEvent && event.filterContext.contains(Filter.Kind.NOTIFICATIONS.kind)) {
|
||||
filterModel.init(Filter.Kind.NOTIFICATIONS)
|
||||
refreshTrigger.value += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
filterModel.kind = Filter.Kind.NOTIFICATIONS
|
||||
viewModelScope.launch {
|
||||
val needsRefresh = filterModel.init(Filter.Kind.NOTIFICATIONS)
|
||||
if (needsRefresh) {
|
||||
refreshTrigger.value++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateNotificationFilters(newFilters: Set<Notification.Type>) {
|
||||
|
|
|
@ -21,10 +21,7 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.PagingData
|
||||
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||
import at.connyduck.calladapter.networkresult.fold
|
||||
import at.connyduck.calladapter.networkresult.getOrElse
|
||||
import at.connyduck.calladapter.networkresult.getOrThrow
|
||||
import com.keylesspalace.tusky.appstore.Event
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.FilterUpdatedEvent
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
|
@ -32,13 +29,11 @@ import com.keylesspalace.tusky.components.preference.PreferencesFragment.Reading
|
|||
import com.keylesspalace.tusky.components.timeline.util.ifExpected
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.FilterV1
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.settings.PrefKeys
|
||||
import com.keylesspalace.tusky.usecase.TimelineCases
|
||||
import com.keylesspalace.tusky.util.isHttpNotFound
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
@ -73,7 +68,6 @@ abstract class TimelineViewModel(
|
|||
this.kind = kind
|
||||
this.id = id
|
||||
this.tags = tags
|
||||
filterModel.kind = kind.toFilterKind()
|
||||
|
||||
if (kind == Kind.HOME) {
|
||||
// Note the variable is "true if filter" but the underlying preference/settings text is "true if show"
|
||||
|
@ -91,10 +85,27 @@ abstract class TimelineViewModel(
|
|||
|
||||
viewModelScope.launch {
|
||||
eventHub.events
|
||||
.collect { event -> handleEvent(event) }
|
||||
.collect { event ->
|
||||
when (event) {
|
||||
is PreferenceChangedEvent -> {
|
||||
onPreferenceChanged(event.preferenceKey)
|
||||
}
|
||||
is FilterUpdatedEvent -> {
|
||||
if (filterContextMatchesKind(this@TimelineViewModel.kind, event.filterContext)) {
|
||||
filterModel.init(kind.toFilterKind())
|
||||
fullReload()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reloadFilters()
|
||||
viewModelScope.launch {
|
||||
val needsRefresh = filterModel.init(kind.toFilterKind())
|
||||
if (needsRefresh) {
|
||||
fullReload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reblog(reblog: Boolean, status: StatusViewData.Concrete): Job = viewModelScope.launch {
|
||||
|
@ -208,11 +219,6 @@ abstract class TimelineViewModel(
|
|||
fullReload()
|
||||
}
|
||||
}
|
||||
FilterV1.HOME, FilterV1.NOTIFICATIONS, FilterV1.THREAD, FilterV1.PUBLIC, FilterV1.ACCOUNT -> {
|
||||
if (filterContextMatchesKind(kind, listOf(key))) {
|
||||
reloadFilters()
|
||||
}
|
||||
}
|
||||
PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA -> {
|
||||
// it is ok if only newly loaded statuses are affected, no need to fully refresh
|
||||
alwaysShowSensitiveMedia =
|
||||
|
@ -224,50 +230,6 @@ abstract class TimelineViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleEvent(event: Event) {
|
||||
when (event) {
|
||||
is PreferenceChangedEvent -> {
|
||||
onPreferenceChanged(event.preferenceKey)
|
||||
}
|
||||
is FilterUpdatedEvent -> {
|
||||
if (filterContextMatchesKind(kind, event.filterContext)) {
|
||||
fullReload()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadFilters() {
|
||||
viewModelScope.launch {
|
||||
api.getFilters().fold(
|
||||
{
|
||||
// After the filters are loaded we need to reload displayed content to apply them.
|
||||
// It can happen during the usage or at startup, when we get statuses before filters.
|
||||
invalidate()
|
||||
},
|
||||
{ throwable ->
|
||||
if (throwable.isHttpNotFound()) {
|
||||
// Fallback to client-side filter code
|
||||
val filters = api.getFiltersV1().getOrElse {
|
||||
Log.e(TAG, "Failed to fetch filters", it)
|
||||
return@launch
|
||||
}
|
||||
filterModel.initWithFilters(
|
||||
filters.filter {
|
||||
filterContextMatchesKind(kind, it.context)
|
||||
}
|
||||
)
|
||||
// After the filters are loaded we need to reload displayed content to apply them.
|
||||
// It can happen during the usage or at startup, when we get statuses before filters.
|
||||
invalidate()
|
||||
} else {
|
||||
Log.e(TAG, "Error getting filters", throwable)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
abstract suspend fun translate(status: StatusViewData.Concrete): NetworkResult<Unit>
|
||||
abstract fun untranslate(status: StatusViewData.Concrete)
|
||||
|
||||
|
|
|
@ -35,12 +35,10 @@ import com.keylesspalace.tusky.components.timeline.util.ifExpected
|
|||
import com.keylesspalace.tusky.db.AccountManager
|
||||
import com.keylesspalace.tusky.db.AppDatabase
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.FilterV1
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.usecase.TimelineCases
|
||||
import com.keylesspalace.tusky.util.isHttpNotFound
|
||||
import com.keylesspalace.tusky.util.toViewData
|
||||
import com.keylesspalace.tusky.viewdata.StatusViewData
|
||||
import com.keylesspalace.tusky.viewdata.TranslationViewData
|
||||
|
@ -101,8 +99,6 @@ class ViewThreadViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadFilters()
|
||||
}
|
||||
|
||||
fun loadThread(id: String) {
|
||||
|
@ -110,6 +106,8 @@ class ViewThreadViewModel @Inject constructor(
|
|||
|
||||
viewModelScope.launch {
|
||||
Log.d(TAG, "Finding status with: $id")
|
||||
val filterCall = async { filterModel.init(Filter.Kind.THREAD) }
|
||||
|
||||
val contextCall = async { api.statusContext(id) }
|
||||
val statusAndAccount = db.timelineStatusDao().getStatusWithAccount(accountManager.activeAccount!!.id, id)
|
||||
|
||||
|
@ -152,6 +150,7 @@ class ViewThreadViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
filterCall.await() // make sure FilterModel is initialized before using it
|
||||
val contextResult = contextCall.await()
|
||||
|
||||
contextResult.fold({ statusContext ->
|
||||
|
@ -420,42 +419,6 @@ class ViewThreadViewModel @Inject constructor(
|
|||
return RevealButtonState.NO_BUTTON
|
||||
}
|
||||
|
||||
private fun loadFilters() {
|
||||
viewModelScope.launch {
|
||||
api.getFilters().fold(
|
||||
{
|
||||
filterModel.kind = Filter.Kind.THREAD
|
||||
updateStatuses()
|
||||
},
|
||||
{ throwable ->
|
||||
if (throwable.isHttpNotFound()) {
|
||||
val filters = api.getFiltersV1().getOrElse {
|
||||
Log.w(TAG, "Failed to fetch filters", it)
|
||||
return@launch
|
||||
}
|
||||
|
||||
filterModel.initWithFilters(
|
||||
filters.filter { filter -> filter.context.contains(FilterV1.THREAD) }
|
||||
)
|
||||
updateStatuses()
|
||||
} else {
|
||||
Log.e(TAG, "Error getting filters", throwable)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateStatuses() {
|
||||
updateSuccess { uiState ->
|
||||
val statuses = uiState.statusViewData.filter()
|
||||
uiState.copy(
|
||||
statusViewData = statuses,
|
||||
revealButton = statuses.getRevealButtonState()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<StatusViewData.Concrete>.filter(): List<StatusViewData.Concrete> {
|
||||
return filter { status ->
|
||||
if (status.isDetailed) {
|
||||
|
|
|
@ -62,14 +62,15 @@ import java.io.File;
|
|||
},
|
||||
// Note: Starting with version 54, database versions in Tusky are always even.
|
||||
// This is to reserve odd version numbers for use by forks.
|
||||
version = 62,
|
||||
version = 64,
|
||||
autoMigrations = {
|
||||
@AutoMigration(from = 48, to = 49),
|
||||
@AutoMigration(from = 49, to = 50, spec = AppDatabase.MIGRATION_49_50.class),
|
||||
@AutoMigration(from = 50, to = 51),
|
||||
@AutoMigration(from = 51, to = 52),
|
||||
@AutoMigration(from = 53, to = 54), // hasDirectMessageBadge in AccountEntity
|
||||
@AutoMigration(from = 56, to = 58) // translationEnabled in InstanceEntity/InstanceInfoEntity
|
||||
@AutoMigration(from = 56, to = 58), // translationEnabled in InstanceEntity/InstanceInfoEntity
|
||||
@AutoMigration(from = 62, to = 64) // filterV2Available in InstanceEntity
|
||||
}
|
||||
)
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
|
|
|
@ -39,4 +39,10 @@ interface InstanceDao {
|
|||
@RewriteQueriesToDropUnusedColumns
|
||||
@Query("SELECT * FROM InstanceEntity WHERE instance = :instance LIMIT 1")
|
||||
suspend fun getEmojiInfo(instance: String): EmojisEntity?
|
||||
|
||||
@Query("UPDATE InstanceEntity SET filterV2Supported = :filterV2Support WHERE instance = :instance")
|
||||
suspend fun setFilterV2Support(instance: String, filterV2Support: Boolean)
|
||||
|
||||
@Query("SELECT filterV2Supported FROM InstanceEntity WHERE instance = :instance LIMIT 1")
|
||||
suspend fun getFilterV2Support(instance: String): Boolean
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
package com.keylesspalace.tusky.db.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.TypeConverters
|
||||
|
@ -41,6 +42,8 @@ data class InstanceEntity(
|
|||
val maxFieldNameLength: Int?,
|
||||
val maxFieldValueLength: Int?,
|
||||
val translationEnabled: Boolean?,
|
||||
// ToDo: Remove this again when filter v1 support is dropped
|
||||
@ColumnInfo(defaultValue = "false") val filterV2Supported: Boolean = false
|
||||
)
|
||||
|
||||
@TypeConverters(Converters::class)
|
||||
|
|
|
@ -28,13 +28,6 @@ data class FilterV1(
|
|||
val irreversible: Boolean,
|
||||
@Json(name = "whole_word") val wholeWord: Boolean
|
||||
) {
|
||||
companion object {
|
||||
const val HOME = "home"
|
||||
const val NOTIFICATIONS = "notifications"
|
||||
const val PUBLIC = "public"
|
||||
const val THREAD = "thread"
|
||||
const val ACCOUNT = "account"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return id.hashCode()
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
package com.keylesspalace.tusky.network
|
||||
|
||||
import android.util.Log
|
||||
import at.connyduck.calladapter.networkresult.fold
|
||||
import at.connyduck.calladapter.networkresult.getOrElse
|
||||
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.FilterV1
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.util.isHttpNotFound
|
||||
import com.keylesspalace.tusky.util.parseAsMastodonHtml
|
||||
import java.util.Date
|
||||
import java.util.regex.Pattern
|
||||
|
@ -11,17 +16,54 @@ import javax.inject.Inject
|
|||
/**
|
||||
* One-stop for status filtering logic using Mastodon's filters.
|
||||
*
|
||||
* 1. You init with [initWithFilters], this compiles regex pattern.
|
||||
* 1. You init with [init], this checks which filter version to use and compiles regex pattern if needed.
|
||||
* 2. You call [shouldFilterStatus] to figure out what to display when you load statuses.
|
||||
*/
|
||||
class FilterModel @Inject constructor() {
|
||||
class FilterModel @Inject constructor(
|
||||
private val instanceInfoRepo: InstanceInfoRepository,
|
||||
private val api: MastodonApi
|
||||
) {
|
||||
private var pattern: Pattern? = null
|
||||
private var v1 = false
|
||||
lateinit var kind: Filter.Kind
|
||||
private lateinit var kind: Filter.Kind
|
||||
|
||||
fun initWithFilters(filters: List<FilterV1>) {
|
||||
v1 = true
|
||||
this.pattern = makeFilter(filters)
|
||||
/**
|
||||
* @param kind the [Filter.Kind] that should be filtered
|
||||
* @return true when filters v1 have been loaded successfully and the currently shown posts may need to be filtered
|
||||
*/
|
||||
suspend fun init(kind: Filter.Kind): Boolean {
|
||||
this.kind = kind
|
||||
|
||||
if (instanceInfoRepo.isFilterV2Supported()) {
|
||||
// nothing to do - Instance supports V2 so posts are filtered by the server
|
||||
return false
|
||||
}
|
||||
|
||||
api.getFilters().fold(
|
||||
{
|
||||
instanceInfoRepo.saveFilterV2Support(true)
|
||||
return false
|
||||
},
|
||||
{ throwable ->
|
||||
if (throwable.isHttpNotFound()) {
|
||||
val filters = api.getFiltersV1().getOrElse {
|
||||
Log.w(TAG, "Failed to fetch filters", it)
|
||||
return false
|
||||
}
|
||||
|
||||
this.v1 = true
|
||||
|
||||
val activeFilters = filters.filter { filter -> filter.context.contains(kind.kind) }
|
||||
|
||||
this.pattern = makeFilter(activeFilters)
|
||||
|
||||
return activeFilters.isNotEmpty()
|
||||
} else {
|
||||
Log.e(TAG, "Error getting filters", throwable)
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun shouldFilterStatus(status: Status): Filter.Action {
|
||||
|
@ -81,6 +123,7 @@ class FilterModel @Inject constructor() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FilterModel"
|
||||
private val ALPHANUMERIC = Pattern.compile("^\\w+$")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
package com.keylesspalace.tusky
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||
import com.keylesspalace.tusky.components.filters.EditFilterActivity
|
||||
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
|
||||
import com.keylesspalace.tusky.entity.Attachment
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.FilterV1
|
||||
|
@ -26,14 +28,20 @@ import com.keylesspalace.tusky.entity.Poll
|
|||
import com.keylesspalace.tusky.entity.PollOption
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.network.FilterModel
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import java.time.Instant
|
||||
import java.util.Date
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.robolectric.annotation.Config
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
|
||||
@Config(sdk = [28])
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
@ -43,12 +51,11 @@ class FilterV1Test {
|
|||
|
||||
@Before
|
||||
fun setup() {
|
||||
filterModel = FilterModel()
|
||||
val filters = listOf(
|
||||
FilterV1(
|
||||
id = "123",
|
||||
phrase = "badWord",
|
||||
context = listOf(FilterV1.HOME),
|
||||
context = listOf(Filter.Kind.HOME.kind),
|
||||
expiresAt = null,
|
||||
irreversible = false,
|
||||
wholeWord = false
|
||||
|
@ -56,7 +63,7 @@ class FilterV1Test {
|
|||
FilterV1(
|
||||
id = "123",
|
||||
phrase = "badWholeWord",
|
||||
context = listOf(FilterV1.HOME, FilterV1.PUBLIC),
|
||||
context = listOf(Filter.Kind.HOME.kind, Filter.Kind.PUBLIC.kind),
|
||||
expiresAt = null,
|
||||
irreversible = false,
|
||||
wholeWord = true
|
||||
|
@ -64,7 +71,7 @@ class FilterV1Test {
|
|||
FilterV1(
|
||||
id = "123",
|
||||
phrase = "@twitter.com",
|
||||
context = listOf(FilterV1.HOME),
|
||||
context = listOf(Filter.Kind.HOME.kind),
|
||||
expiresAt = null,
|
||||
irreversible = false,
|
||||
wholeWord = true
|
||||
|
@ -72,7 +79,7 @@ class FilterV1Test {
|
|||
FilterV1(
|
||||
id = "123",
|
||||
phrase = "#hashtag",
|
||||
context = listOf(FilterV1.HOME),
|
||||
context = listOf(Filter.Kind.HOME.kind),
|
||||
expiresAt = null,
|
||||
irreversible = false,
|
||||
wholeWord = true
|
||||
|
@ -80,7 +87,7 @@ class FilterV1Test {
|
|||
FilterV1(
|
||||
id = "123",
|
||||
phrase = "expired",
|
||||
context = listOf(FilterV1.HOME),
|
||||
context = listOf(Filter.Kind.HOME.kind),
|
||||
expiresAt = Date.from(Instant.now().minusSeconds(10)),
|
||||
irreversible = false,
|
||||
wholeWord = true
|
||||
|
@ -88,7 +95,7 @@ class FilterV1Test {
|
|||
FilterV1(
|
||||
id = "123",
|
||||
phrase = "unexpired",
|
||||
context = listOf(FilterV1.HOME),
|
||||
context = listOf(Filter.Kind.HOME.kind),
|
||||
expiresAt = Date.from(Instant.now().plusSeconds(3600)),
|
||||
irreversible = false,
|
||||
wholeWord = true
|
||||
|
@ -96,14 +103,27 @@ class FilterV1Test {
|
|||
FilterV1(
|
||||
id = "123",
|
||||
phrase = "href",
|
||||
context = listOf(FilterV1.HOME),
|
||||
context = listOf(Filter.Kind.HOME.kind),
|
||||
expiresAt = null,
|
||||
irreversible = false,
|
||||
wholeWord = false
|
||||
)
|
||||
)
|
||||
|
||||
filterModel.initWithFilters(filters)
|
||||
val api: MastodonApi = mock {
|
||||
onBlocking { getFiltersV1() } doReturn NetworkResult.success(filters)
|
||||
onBlocking { getFilters() } doReturn NetworkResult.failure(
|
||||
HttpException(Response.error<Any>(404, "".toResponseBody()))
|
||||
)
|
||||
}
|
||||
val instanceInfoRepo: InstanceInfoRepository = mock {
|
||||
onBlocking { isFilterV2Supported() } doReturn false
|
||||
}
|
||||
|
||||
filterModel = FilterModel(instanceInfoRepo, api)
|
||||
runBlocking {
|
||||
filterModel.init(Filter.Kind.HOME)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.test.platform.app.InstrumentationRegistry
|
|||
import at.connyduck.calladapter.networkresult.NetworkResult
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.StatusChangedEvent
|
||||
import com.keylesspalace.tusky.components.instanceinfo.InstanceInfoRepository
|
||||
import com.keylesspalace.tusky.components.timeline.fakeStatus
|
||||
import com.keylesspalace.tusky.components.timeline.fakeStatusViewData
|
||||
import com.keylesspalace.tusky.db.AccountManager
|
||||
|
@ -81,8 +82,11 @@ class ViewThreadViewModelTest {
|
|||
api = mock {
|
||||
onBlocking { getFilters() } doReturn NetworkResult.success(emptyList())
|
||||
}
|
||||
val instanceInfoRepo: InstanceInfoRepository = mock {
|
||||
onBlocking { isFilterV2Supported() } doReturn false
|
||||
}
|
||||
eventHub = EventHub()
|
||||
val filterModel = FilterModel()
|
||||
val filterModel = FilterModel(instanceInfoRepo, api)
|
||||
val timelineCases = TimelineCases(api, eventHub)
|
||||
val accountManager: AccountManager = mock {
|
||||
on { activeAccount } doReturn AccountEntity(
|
||||
|
|
Loading…
Reference in New Issue