mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 15:41:52 +01:00
Fixes to CoroutineScope
This includes removing what I thought was a good helper method for a list of preferences Also using a base scope for controllers (others will have to be updated later)
This commit is contained in:
parent
8753d188d1
commit
0b4019a1cb
@ -43,17 +43,6 @@ fun <T> com.tfcporciuncula.flow.Preference<T>.asImmediateFlowIn(scope: Coroutine
|
|||||||
.launchIn(scope)
|
.launchIn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T> Collection<com.tfcporciuncula.flow.Preference<out T>>.asFlowsIn(scope: CoroutineScope, dropFirst: Boolean = false, block: () -> Unit): Collection<Job> {
|
|
||||||
return map { pref ->
|
|
||||||
pref.asFlow()
|
|
||||||
.apply {
|
|
||||||
if (dropFirst) drop(1)
|
|
||||||
}
|
|
||||||
.onEach { block() }
|
|
||||||
.launchIn(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun com.tfcporciuncula.flow.Preference<Boolean>.toggle() = set(!get())
|
fun com.tfcporciuncula.flow.Preference<Boolean>.toggle() = set(!get())
|
||||||
|
|
||||||
private class DateFormatConverter : Preference.Adapter<DateFormat> {
|
private class DateFormatConverter : Preference.Adapter<DateFormat> {
|
||||||
|
@ -13,12 +13,16 @@ import com.bluelinelabs.conductor.ControllerChangeHandler
|
|||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
import com.bluelinelabs.conductor.ControllerChangeType
|
||||||
import com.bluelinelabs.conductor.RestoreViewOnCreateController
|
import com.bluelinelabs.conductor.RestoreViewOnCreateController
|
||||||
import eu.kanade.tachiyomi.util.view.removeQueryListener
|
import eu.kanade.tachiyomi.util.view.removeQueryListener
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
||||||
RestoreViewOnCreateController(bundle) {
|
RestoreViewOnCreateController(bundle) {
|
||||||
|
|
||||||
lateinit var binding: VB
|
lateinit var binding: VB
|
||||||
|
lateinit var viewScope: CoroutineScope
|
||||||
|
|
||||||
val isBindingInitialized get() = this::binding.isInitialized
|
val isBindingInitialized get() = this::binding.isInitialized
|
||||||
init {
|
init {
|
||||||
@ -29,6 +33,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun preCreateView(controller: Controller) {
|
override fun preCreateView(controller: Controller) {
|
||||||
|
viewScope = MainScope()
|
||||||
Timber.d("Create view for ${controller.instance()}")
|
Timber.d("Create view for ${controller.instance()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +46,7 @@ abstract class BaseController<VB : ViewBinding>(bundle: Bundle? = null) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun preDestroyView(controller: Controller, view: View) {
|
override fun preDestroyView(controller: Controller, view: View) {
|
||||||
|
viewScope.cancel()
|
||||||
Timber.d("Destroy view for ${controller.instance()}")
|
Timber.d("Destroy view for ${controller.instance()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,6 @@ import eu.kanade.tachiyomi.util.view.openInBrowser
|
|||||||
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
import eu.kanade.tachiyomi.util.view.scrollViewWith
|
||||||
import eu.kanade.tachiyomi.util.view.snack
|
import eu.kanade.tachiyomi.util.view.snack
|
||||||
import eu.kanade.tachiyomi.widget.preference.ListMatPreference
|
import eu.kanade.tachiyomi.widget.preference.ListMatPreference
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -64,7 +63,6 @@ class ExtensionDetailsController(bundle: Bundle? = null) :
|
|||||||
|
|
||||||
private val preferences: PreferencesHelper = Injekt.get()
|
private val preferences: PreferencesHelper = Injekt.get()
|
||||||
|
|
||||||
private val viewScope = MainScope()
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|||||||
import androidx.core.view.GestureDetectorCompat
|
import androidx.core.view.GestureDetectorCompat
|
||||||
import androidx.core.view.isInvisible
|
import androidx.core.view.isInvisible
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@ -53,7 +52,6 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.asFlowsIn
|
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.databinding.LibraryControllerBinding
|
import eu.kanade.tachiyomi.databinding.LibraryControllerBinding
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
@ -99,11 +97,11 @@ import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
|||||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.widget.EndAnimatorListener
|
import eu.kanade.tachiyomi.widget.EndAnimatorListener
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -150,8 +148,6 @@ class LibraryController(
|
|||||||
var singleCategory: Boolean = false
|
var singleCategory: Boolean = false
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Library search query.
|
* Library search query.
|
||||||
*/
|
*/
|
||||||
@ -835,9 +831,14 @@ class LibraryController(
|
|||||||
preferences.uniformGrid(),
|
preferences.uniformGrid(),
|
||||||
preferences.gridSize(),
|
preferences.gridSize(),
|
||||||
preferences.unreadBadgeType()
|
preferences.unreadBadgeType()
|
||||||
).asFlowsIn(scope, true) {
|
).forEach {
|
||||||
|
it.asFlow()
|
||||||
|
.drop(1)
|
||||||
|
.onEach {
|
||||||
reattachAdapter()
|
reattachAdapter()
|
||||||
}
|
}
|
||||||
|
.launchIn(viewScope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
||||||
@ -885,7 +886,6 @@ class LibraryController(
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
if (::presenter.isInitialized) presenter.onDestroy()
|
if (::presenter.isInitialized) presenter.onDestroy()
|
||||||
scope.cancel()
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -897,6 +897,7 @@ class LibraryController(
|
|||||||
}
|
}
|
||||||
displaySheet?.dismiss()
|
displaySheet?.dismiss()
|
||||||
displaySheet = null
|
displaySheet = null
|
||||||
|
presenter.cancelScope()
|
||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -913,11 +914,6 @@ class LibraryController(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
adapter.setItems(mangaMap)
|
adapter.setItems(mangaMap)
|
||||||
if (binding.libraryGridRecycler.recycler.itemAnimator == null) {
|
|
||||||
binding.libraryGridRecycler.recycler.post {
|
|
||||||
binding.libraryGridRecycler.recycler.itemAnimator = DefaultItemAnimator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
singleCategory = presenter.categories.size <= 1
|
singleCategory = presenter.categories.size <= 1
|
||||||
showDropdown()
|
showDropdown()
|
||||||
binding.progress.isVisible = false
|
binding.progress.isVisible = false
|
||||||
@ -951,9 +947,7 @@ class LibraryController(
|
|||||||
listOf(activityBinding?.toolbar, binding.headerTitle).forEach {
|
listOf(activityBinding?.toolbar, binding.headerTitle).forEach {
|
||||||
it?.setOnClickListener {
|
it?.setOnClickListener {
|
||||||
val recycler = binding.libraryGridRecycler.recycler
|
val recycler = binding.libraryGridRecycler.recycler
|
||||||
if (singleCategory) {
|
if (!singleCategory) {
|
||||||
recycler.scrollToPosition(0)
|
|
||||||
} else {
|
|
||||||
showCategories(recycler.translationY == 0f)
|
showCategories(recycler.translationY == 0f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import eu.kanade.tachiyomi.util.system.executeOnIO
|
|||||||
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.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -86,6 +87,10 @@ class LibraryPresenter(
|
|||||||
val libraryIsGrouped
|
val libraryIsGrouped
|
||||||
get() = groupType != UNGROUPED
|
get() = groupType != UNGROUPED
|
||||||
|
|
||||||
|
fun cancelScope() {
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
/** Save the current list to speed up loading later */
|
/** Save the current list to speed up loading later */
|
||||||
fun onDestroy() {
|
fun onDestroy() {
|
||||||
lastLibraryItems = libraryItems
|
lastLibraryItems = libraryItems
|
||||||
@ -105,6 +110,7 @@ class LibraryPresenter(
|
|||||||
if (categories.isEmpty()) {
|
if (categories.isEmpty()) {
|
||||||
categories = lastCategories ?: db.getCategories().executeAsBlocking().toMutableList()
|
categories = lastCategories ?: db.getCategories().executeAsBlocking().toMutableList()
|
||||||
}
|
}
|
||||||
|
scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val library = withContext(Dispatchers.IO) { getLibraryFromDB() }
|
val library = withContext(Dispatchers.IO) { getLibraryFromDB() }
|
||||||
library.apply {
|
library.apply {
|
||||||
|
@ -181,7 +181,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
|
|||||||
filterOrder = it
|
filterOrder = it
|
||||||
clearFilters()
|
clearFilters()
|
||||||
}
|
}
|
||||||
.launchIn(controller.scope)
|
.launchIn(controller.viewScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stateChanged(state: Int) {
|
private fun stateChanged(state: Int) {
|
||||||
|
@ -37,7 +37,6 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.asFlowsIn
|
|
||||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn
|
import eu.kanade.tachiyomi.data.preference.asImmediateFlowIn
|
||||||
import eu.kanade.tachiyomi.data.preference.toggle
|
import eu.kanade.tachiyomi.data.preference.toggle
|
||||||
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
|
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
|
||||||
@ -637,7 +636,11 @@ class ReaderActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
listOf(preferences.cropBorders(), preferences.cropBordersWebtoon())
|
listOf(preferences.cropBorders(), preferences.cropBordersWebtoon())
|
||||||
.asFlowsIn(scope) { updateCropBordersShortcut() }
|
.forEach { pref ->
|
||||||
|
pref.asFlow()
|
||||||
|
.onEach { updateCropBordersShortcut() }
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
preferences.rotation().asImmediateFlowIn(scope) { updateRotationShortcut(it) }
|
preferences.rotation().asImmediateFlowIn(scope) { updateRotationShortcut(it) }
|
||||||
|
|
||||||
binding.chaptersSheet.shiftPageButton.setOnClickListener {
|
binding.chaptersSheet.shiftPageButton.setOnClickListener {
|
||||||
|
@ -54,10 +54,6 @@ import eu.kanade.tachiyomi.util.view.snack
|
|||||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
@ -89,12 +85,10 @@ class RecentsController(bundle: Bundle? = null) :
|
|||||||
presenter.toggleGroupRecents(viewType, false)
|
presenter.toggleGroupRecents(viewType, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val adapterScope: CoroutineScope = CoroutineScope(Job() + Dispatchers.Main)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adapter containing the recent manga.
|
* Adapter containing the recent manga.
|
||||||
*/
|
*/
|
||||||
private var adapter = RecentMangaAdapter(this)
|
private lateinit var adapter: RecentMangaAdapter
|
||||||
var displaySheet: TabbedRecentsOptionsSheet? = null
|
var displaySheet: TabbedRecentsOptionsSheet? = null
|
||||||
|
|
||||||
private var progressItem: ProgressItem? = null
|
private var progressItem: ProgressItem? = null
|
||||||
@ -383,14 +377,13 @@ class RecentsController(bundle: Bundle? = null) :
|
|||||||
snack?.dismiss()
|
snack?.dismiss()
|
||||||
presenter.onDestroy()
|
presenter.onDestroy()
|
||||||
snack = null
|
snack = null
|
||||||
adapterScope.cancel()
|
|
||||||
presenter.cancelScope()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
override fun onDestroyView(view: View) {
|
||||||
super.onDestroyView(view)
|
super.onDestroyView(view)
|
||||||
displaySheet?.dismiss()
|
displaySheet?.dismiss()
|
||||||
displaySheet = null
|
displaySheet = null
|
||||||
|
presenter.cancelScope()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() = presenter.getRecents()
|
fun refresh() = presenter.getRecents()
|
||||||
@ -497,7 +490,7 @@ class RecentsController(bundle: Bundle? = null) :
|
|||||||
|
|
||||||
override fun getViewType(): Int = presenter.viewType
|
override fun getViewType(): Int = presenter.viewType
|
||||||
|
|
||||||
override fun scope() = adapterScope
|
override fun scope() = viewScope
|
||||||
|
|
||||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
override fun onItemClick(view: View?, position: Int): Boolean {
|
||||||
val item = adapter.getItem(position) ?: return false
|
val item = adapter.getItem(position) ?: return false
|
||||||
|
@ -12,13 +12,15 @@ import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
|||||||
import eu.kanade.tachiyomi.data.library.LibraryServiceListener
|
import eu.kanade.tachiyomi.data.library.LibraryServiceListener
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.asFlowsIn
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||||
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.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -71,17 +73,6 @@ class RecentsPresenter(
|
|||||||
private val isOnFirstPage: Boolean
|
private val isOnFirstPage: Boolean
|
||||||
get() = pageOffset == 0
|
get() = pageOffset == 0
|
||||||
|
|
||||||
init {
|
|
||||||
listOf(
|
|
||||||
preferences.groupChaptersHistory(),
|
|
||||||
preferences.showReadInAllRecents(),
|
|
||||||
preferences.groupChaptersUpdates()
|
|
||||||
).asFlowsIn(scope, true) {
|
|
||||||
resetOffsets()
|
|
||||||
getRecents()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onCreate() {
|
fun onCreate() {
|
||||||
downloadManager.addListener(this)
|
downloadManager.addListener(this)
|
||||||
LibraryUpdateService.setListener(this)
|
LibraryUpdateService.setListener(this)
|
||||||
@ -92,6 +83,20 @@ class RecentsPresenter(
|
|||||||
lastRecents = null
|
lastRecents = null
|
||||||
}
|
}
|
||||||
getRecents()
|
getRecents()
|
||||||
|
scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
listOf(
|
||||||
|
preferences.groupChaptersHistory(),
|
||||||
|
preferences.showReadInAllRecents(),
|
||||||
|
preferences.groupChaptersUpdates()
|
||||||
|
).forEach {
|
||||||
|
it.asFlow()
|
||||||
|
.drop(1)
|
||||||
|
.onEach {
|
||||||
|
resetOffsets()
|
||||||
|
getRecents()
|
||||||
|
}
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRecents(updatePageCount: Boolean = false) {
|
fun getRecents(updatePageCount: Boolean = false) {
|
||||||
|
Loading…
Reference in New Issue
Block a user