Use Compose on Clear Database screen (#7639)
This commit is contained in:
parent
4774deb1ef
commit
99ac30e59f
@ -2,12 +2,12 @@ package eu.kanade.data.source
|
|||||||
|
|
||||||
import eu.kanade.data.DatabaseHandler
|
import eu.kanade.data.DatabaseHandler
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.domain.source.model.SourceWithCount
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import eu.kanade.tachiyomi.source.Source as LoadedSource
|
|
||||||
|
|
||||||
class SourceRepositoryImpl(
|
class SourceRepositoryImpl(
|
||||||
private val sourceManager: SourceManager,
|
private val sourceManager: SourceManager,
|
||||||
@ -40,12 +40,12 @@ class SourceRepositoryImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSourcesWithNonLibraryManga(): Flow<List<Pair<LoadedSource, Long>>> {
|
override fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>> {
|
||||||
val sourceIdWithNonLibraryManga = handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
|
val sourceIdWithNonLibraryManga = handler.subscribeToList { mangasQueries.getSourceIdsWithNonLibraryManga() }
|
||||||
return sourceIdWithNonLibraryManga.map { sourceId ->
|
return sourceIdWithNonLibraryManga.map { sourceId ->
|
||||||
sourceId.map { (sourceId, count) ->
|
sourceId.map { (sourceId, count) ->
|
||||||
val source = sourceManager.getOrStub(sourceId)
|
val source = sourceManager.getOrStub(sourceId)
|
||||||
source to count
|
SourceWithCount(sourceMapper(source), count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package eu.kanade.domain.source.interactor
|
package eu.kanade.domain.source.interactor
|
||||||
|
|
||||||
|
import eu.kanade.domain.source.model.SourceWithCount
|
||||||
import eu.kanade.domain.source.repository.SourceRepository
|
import eu.kanade.domain.source.repository.SourceRepository
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
class GetSourcesWithNonLibraryManga(
|
class GetSourcesWithNonLibraryManga(
|
||||||
private val repository: SourceRepository,
|
private val repository: SourceRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun subscribe(): Flow<List<Pair<Source, Long>>> {
|
fun subscribe(): Flow<List<SourceWithCount>> {
|
||||||
return repository.getSourcesWithNonLibraryManga()
|
return repository.getSourcesWithNonLibraryManga()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.domain.source.model
|
||||||
|
|
||||||
|
data class SourceWithCount(
|
||||||
|
val source: Source,
|
||||||
|
val count: Long,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val id: Long
|
||||||
|
get() = source.id
|
||||||
|
|
||||||
|
val name: String
|
||||||
|
get() = source.name
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package eu.kanade.domain.source.repository
|
package eu.kanade.domain.source.repository
|
||||||
|
|
||||||
import eu.kanade.domain.source.model.Source
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.domain.source.model.SourceWithCount
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import eu.kanade.tachiyomi.source.Source as LoadedSource
|
|
||||||
|
|
||||||
interface SourceRepository {
|
interface SourceRepository {
|
||||||
|
|
||||||
@ -12,5 +12,5 @@ interface SourceRepository {
|
|||||||
|
|
||||||
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
|
fun getSourcesWithFavoriteCount(): Flow<List<Pair<Source, Long>>>
|
||||||
|
|
||||||
fun getSourcesWithNonLibraryManga(): Flow<List<Pair<LoadedSource, Long>>>
|
fun getSourcesWithNonLibraryManga(): Flow<List<SourceWithCount>>
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.database
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import eu.kanade.presentation.components.Scaffold
|
||||||
|
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseContent
|
||||||
|
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseDeleteDialog
|
||||||
|
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseFloatingActionButton
|
||||||
|
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseToolbar
|
||||||
|
import eu.kanade.presentation.util.plus
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabasePresenter
|
||||||
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClearDatabaseScreen(
|
||||||
|
presenter: ClearDatabasePresenter,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
ClearDatabaseToolbar(
|
||||||
|
state = presenter,
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
onClickSelectAll = { presenter.selectAll() },
|
||||||
|
onClickInvertSelection = { presenter.invertSelection() },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
ClearDatabaseFloatingActionButton(
|
||||||
|
isVisible = presenter.selection.isNotEmpty(),
|
||||||
|
onClickDelete = {
|
||||||
|
presenter.dialog = ClearDatabasePresenter.Dialog.Delete(presenter.selection)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { paddingValues ->
|
||||||
|
ClearDatabaseContent(
|
||||||
|
state = presenter,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
onClickSelection = { source ->
|
||||||
|
presenter.toggleSelection(source)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (presenter.dialog is ClearDatabasePresenter.Dialog.Delete) {
|
||||||
|
ClearDatabaseDeleteDialog(
|
||||||
|
onDismissRequest = { presenter.dialog = null },
|
||||||
|
onDelete = {
|
||||||
|
presenter.removeMangaBySourceId((presenter.dialog as ClearDatabasePresenter.Dialog.Delete).sourceIds)
|
||||||
|
presenter.clearSelection()
|
||||||
|
context.toast(R.string.clear_database_completed)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.database
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import eu.kanade.domain.source.model.SourceWithCount
|
||||||
|
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabasePresenter
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
interface ClearDatabaseState {
|
||||||
|
val items: List<SourceWithCount>
|
||||||
|
val selection: List<Long>
|
||||||
|
val isEmpty: Boolean
|
||||||
|
var dialog: ClearDatabasePresenter.Dialog?
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ClearDatabaseState(): ClearDatabaseState {
|
||||||
|
return ClearDatabaseStateImpl()
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClearDatabaseStateImpl : ClearDatabaseState {
|
||||||
|
override var items: List<SourceWithCount> by mutableStateOf(emptyList())
|
||||||
|
override var selection: List<Long> by mutableStateOf(emptyList())
|
||||||
|
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
||||||
|
override var dialog: ClearDatabasePresenter.Dialog? by mutableStateOf(null)
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.database.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.presentation.components.EmptyScreen
|
||||||
|
import eu.kanade.presentation.components.FastScrollLazyColumn
|
||||||
|
import eu.kanade.presentation.more.settings.database.ClearDatabaseState
|
||||||
|
import eu.kanade.presentation.util.plus
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClearDatabaseContent(
|
||||||
|
state: ClearDatabaseState,
|
||||||
|
contentPadding: PaddingValues,
|
||||||
|
onClickSelection: (Source) -> Unit,
|
||||||
|
) {
|
||||||
|
Crossfade(targetState = state.isEmpty.not()) { _state ->
|
||||||
|
when (_state) {
|
||||||
|
true -> FastScrollLazyColumn(
|
||||||
|
contentPadding = contentPadding + WindowInsets.navigationBars.asPaddingValues(),
|
||||||
|
) {
|
||||||
|
items(state.items) { sourceWithCount ->
|
||||||
|
ClearDatabaseItem(
|
||||||
|
source = sourceWithCount.source,
|
||||||
|
count = sourceWithCount.count,
|
||||||
|
isSelected = state.selection.contains(sourceWithCount.id),
|
||||||
|
onClickSelect = { onClickSelection(sourceWithCount.source) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false -> EmptyScreen(message = stringResource(id = R.string.database_clean))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.database.components
|
||||||
|
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.presentation.components.TextButton
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClearDatabaseDeleteDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onDelete: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDelete) {
|
||||||
|
Text(text = stringResource(id = android.R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(id = android.R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(id = R.string.clear_database_confirmation))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.database.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.presentation.components.ExtendedFloatingActionButton
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClearDatabaseFloatingActionButton(
|
||||||
|
isVisible: Boolean,
|
||||||
|
onClickDelete: () -> Unit,
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isVisible,
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
ExtendedFloatingActionButton(
|
||||||
|
modifier = Modifier.navigationBarsPadding(),
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(id = R.string.action_delete))
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(Icons.Outlined.Delete, contentDescription = "")
|
||||||
|
},
|
||||||
|
onClick = onClickDelete,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.database.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Checkbox
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.presentation.browse.components.SourceIcon
|
||||||
|
import eu.kanade.presentation.util.selectedBackground
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClearDatabaseItem(
|
||||||
|
source: Source,
|
||||||
|
count: Long,
|
||||||
|
isSelected: Boolean,
|
||||||
|
onClickSelect: () -> Unit,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.selectedBackground(isSelected)
|
||||||
|
.clickable(onClick = onClickSelect)
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.height(56.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
SourceIcon(source = source)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 8.dp)
|
||||||
|
.weight(1f),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = source.nameWithLanguage,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
Text(text = stringResource(id = R.string.clear_database_source_item_count, count))
|
||||||
|
}
|
||||||
|
Checkbox(
|
||||||
|
checked = isSelected,
|
||||||
|
onCheckedChange = { onClickSelect() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.presentation.more.settings.database.components
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.FlipToBack
|
||||||
|
import androidx.compose.material.icons.outlined.SelectAll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import eu.kanade.presentation.components.AppBar
|
||||||
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
|
import eu.kanade.presentation.more.settings.database.ClearDatabaseState
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ClearDatabaseToolbar(
|
||||||
|
state: ClearDatabaseState,
|
||||||
|
navigateUp: () -> Unit,
|
||||||
|
onClickSelectAll: () -> Unit,
|
||||||
|
onClickInvertSelection: () -> Unit,
|
||||||
|
) {
|
||||||
|
AppBar(
|
||||||
|
title = stringResource(id = R.string.pref_clear_database),
|
||||||
|
navigateUp = navigateUp,
|
||||||
|
actions = {
|
||||||
|
if (state.isEmpty.not()) {
|
||||||
|
AppBarActions(
|
||||||
|
actions = listOf(
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(id = R.string.action_select_all),
|
||||||
|
icon = Icons.Outlined.SelectAll,
|
||||||
|
onClick = onClickSelectAll,
|
||||||
|
),
|
||||||
|
AppBar.Action(
|
||||||
|
title = stringResource(id = R.string.action_select_all),
|
||||||
|
icon = Icons.Outlined.FlipToBack,
|
||||||
|
onClick = onClickInvertSelection,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
@ -1,171 +1,20 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting.database
|
package eu.kanade.tachiyomi.ui.setting.database
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import androidx.compose.runtime.Composable
|
||||||
import android.app.Dialog
|
import eu.kanade.presentation.more.settings.database.ClearDatabaseScreen
|
||||||
import android.os.Bundle
|
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import androidx.core.view.forEach
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.Payload
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.ClearDatabaseControllerBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FabController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
|
|
||||||
class ClearDatabaseController :
|
class ClearDatabaseController : FullComposeController<ClearDatabasePresenter>() {
|
||||||
NucleusController<ClearDatabaseControllerBinding, ClearDatabasePresenter>(),
|
|
||||||
FlexibleAdapter.OnItemClickListener,
|
|
||||||
FlexibleAdapter.OnUpdateListener,
|
|
||||||
FabController {
|
|
||||||
|
|
||||||
private var recycler: RecyclerView? = null
|
|
||||||
private var adapter: FlexibleAdapter<ClearDatabaseSourceItem>? = null
|
|
||||||
|
|
||||||
private var menu: Menu? = null
|
|
||||||
|
|
||||||
private var actionFab: ExtendedFloatingActionButton? = null
|
|
||||||
|
|
||||||
init {
|
|
||||||
setHasOptionsMenu(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createBinding(inflater: LayoutInflater): ClearDatabaseControllerBinding {
|
|
||||||
return ClearDatabaseControllerBinding.inflate(inflater)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPresenter(): ClearDatabasePresenter {
|
override fun createPresenter(): ClearDatabasePresenter {
|
||||||
return ClearDatabasePresenter()
|
return ClearDatabasePresenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
@Composable
|
||||||
return activity?.getString(R.string.pref_clear_database)
|
override fun ComposeContent() {
|
||||||
}
|
ClearDatabaseScreen(
|
||||||
|
presenter = presenter,
|
||||||
override fun onViewCreated(view: View) {
|
navigateUp = { router.popCurrentController() },
|
||||||
super.onViewCreated(view)
|
)
|
||||||
|
|
||||||
binding.recycler.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter = FlexibleAdapter<ClearDatabaseSourceItem>(null, this, true)
|
|
||||||
binding.recycler.adapter = adapter
|
|
||||||
binding.recycler.layoutManager = LinearLayoutManager(view.context)
|
|
||||||
binding.recycler.setHasFixedSize(true)
|
|
||||||
adapter?.fastScroller = binding.fastScroller
|
|
||||||
recycler = binding.recycler
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
adapter = null
|
|
||||||
super.onDestroyView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
inflater.inflate(R.menu.generic_selection, menu)
|
|
||||||
this.menu = menu
|
|
||||||
menu.forEach { menuItem -> menuItem.isVisible = (adapter?.itemCount ?: 0) > 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
val adapter = adapter ?: return false
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_select_all -> adapter.selectAll()
|
|
||||||
R.id.action_select_inverse -> {
|
|
||||||
adapter.currentItems.forEachIndexed { index, _ ->
|
|
||||||
adapter.toggleSelection(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateFab()
|
|
||||||
adapter.notifyItemRangeChanged(0, adapter.itemCount, Payload.SELECTION)
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUpdateEmptyView(size: Int) {
|
|
||||||
if (size > 0) {
|
|
||||||
binding.emptyView.hide()
|
|
||||||
} else {
|
|
||||||
binding.emptyView.show(activity!!.getString(R.string.database_clean))
|
|
||||||
}
|
|
||||||
|
|
||||||
menu?.forEach { menuItem -> menuItem.isVisible = size > 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemClick(view: View?, position: Int): Boolean {
|
|
||||||
val adapter = adapter ?: return false
|
|
||||||
adapter.toggleSelection(position)
|
|
||||||
adapter.notifyItemChanged(position, Payload.SELECTION)
|
|
||||||
updateFab()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setItems(items: List<ClearDatabaseSourceItem>) {
|
|
||||||
adapter?.updateDataSet(items)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun configureFab(fab: ExtendedFloatingActionButton) {
|
|
||||||
fab.setIconResource(R.drawable.ic_delete_24dp)
|
|
||||||
fab.setText(R.string.action_delete)
|
|
||||||
fab.hide()
|
|
||||||
fab.setOnClickListener {
|
|
||||||
val ctrl = ClearDatabaseSourcesDialog()
|
|
||||||
ctrl.targetController = this
|
|
||||||
ctrl.showDialog(router)
|
|
||||||
}
|
|
||||||
actionFab = fab
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateFab() {
|
|
||||||
val adapter = adapter ?: return
|
|
||||||
if (adapter.selectedItemCount > 0) {
|
|
||||||
actionFab?.show()
|
|
||||||
} else {
|
|
||||||
actionFab?.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cleanupFab(fab: ExtendedFloatingActionButton) {
|
|
||||||
actionFab?.setOnClickListener(null)
|
|
||||||
actionFab = null
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClearDatabaseSourcesDialog : DialogController() {
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setMessage(R.string.clear_database_confirmation)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
(targetController as? ClearDatabaseController)?.clearDatabaseForSelectedSources()
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
|
||||||
private fun clearDatabaseForSelectedSources() {
|
|
||||||
val adapter = adapter ?: return
|
|
||||||
val selectedSourceIds = adapter.selectedPositions.mapNotNull { position ->
|
|
||||||
adapter.getItem(position)?.source?.id
|
|
||||||
}
|
|
||||||
presenter.clearDatabaseForSourceIds(selectedSourceIds)
|
|
||||||
actionFab!!.isVisible = false
|
|
||||||
adapter.clearSelection()
|
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
activity?.toast(R.string.clear_database_completed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,21 @@ package eu.kanade.tachiyomi.ui.setting.database
|
|||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
||||||
|
import eu.kanade.domain.source.model.Source
|
||||||
|
import eu.kanade.presentation.more.settings.database.ClearDatabaseState
|
||||||
|
import eu.kanade.presentation.more.settings.database.ClearDatabaseStateImpl
|
||||||
import eu.kanade.tachiyomi.Database
|
import eu.kanade.tachiyomi.Database
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ClearDatabasePresenter(
|
class ClearDatabasePresenter(
|
||||||
|
private val state: ClearDatabaseStateImpl = ClearDatabaseState() as ClearDatabaseStateImpl,
|
||||||
private val database: Database = Injekt.get(),
|
private val database: Database = Injekt.get(),
|
||||||
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get(),
|
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get(),
|
||||||
) : BasePresenter<ClearDatabaseController>() {
|
) : BasePresenter<ClearDatabaseController>(), ClearDatabaseState by state {
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
override fun onCreate(savedState: Bundle?) {
|
||||||
super.onCreate(savedState)
|
super.onCreate(savedState)
|
||||||
@ -21,17 +24,39 @@ class ClearDatabasePresenter(
|
|||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
getSourcesWithNonLibraryManga.subscribe()
|
getSourcesWithNonLibraryManga.subscribe()
|
||||||
.collectLatest { list ->
|
.collectLatest { list ->
|
||||||
val items = list
|
state.items = list.sortedBy { it.name }
|
||||||
.map { (source, count) -> ClearDatabaseSourceItem(source, count) }
|
|
||||||
.sortedBy { it.source.name }
|
|
||||||
|
|
||||||
withUIContext { view?.setItems(items) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearDatabaseForSourceIds(sources: List<Long>) {
|
fun removeMangaBySourceId(sourceIds: List<Long>) {
|
||||||
database.mangasQueries.deleteMangasNotInLibraryBySourceIds(sources)
|
database.mangasQueries.deleteMangasNotInLibraryBySourceIds(sourceIds)
|
||||||
database.historyQueries.removeResettedHistory()
|
database.historyQueries.removeResettedHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleSelection(source: Source) {
|
||||||
|
val mutableList = state.selection.toMutableList()
|
||||||
|
if (mutableList.contains(source.id)) {
|
||||||
|
mutableList.remove(source.id)
|
||||||
|
} else {
|
||||||
|
mutableList.add(source.id)
|
||||||
|
}
|
||||||
|
state.selection = mutableList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSelection() {
|
||||||
|
state.selection = emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun selectAll() {
|
||||||
|
state.selection = state.items.map { it.id }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invertSelection() {
|
||||||
|
state.selection = state.items.map { it.id }.filterNot { it in state.selection }
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Dialog {
|
||||||
|
data class Delete(val sourceIds: List<Long>) : Dialog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.ui.setting.database
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
|
||||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.ClearDatabaseSourceItemBinding
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
|
||||||
import eu.kanade.tachiyomi.source.icon
|
|
||||||
|
|
||||||
data class ClearDatabaseSourceItem(val source: Source, private val mangaCount: Long) : AbstractFlexibleItem<ClearDatabaseSourceItem.Holder>() {
|
|
||||||
|
|
||||||
override fun getLayoutRes(): Int {
|
|
||||||
return R.layout.clear_database_source_item
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): Holder {
|
|
||||||
return Holder(view, adapter)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>?, holder: Holder?, position: Int, payloads: MutableList<Any>?) {
|
|
||||||
holder?.bind(source, mangaCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
class Holder(view: View, adapter: FlexibleAdapter<*>) : FlexibleViewHolder(view, adapter) {
|
|
||||||
|
|
||||||
private val binding = ClearDatabaseSourceItemBinding.bind(view)
|
|
||||||
|
|
||||||
fun bind(source: Source, count: Long) {
|
|
||||||
binding.title.text = source.toString()
|
|
||||||
binding.description.text = itemView.context.getString(R.string.clear_database_source_item_count, count)
|
|
||||||
|
|
||||||
itemView.post {
|
|
||||||
when {
|
|
||||||
source.icon() != null && source.id != LocalSource.ID ->
|
|
||||||
binding.thumbnail.setImageDrawable(source.icon())
|
|
||||||
else -> binding.thumbnail.setImageResource(R.mipmap.ic_local_source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.checkbox.isChecked = (bindingAdapter as FlexibleAdapter<*>).isSelected(bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/recycler"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:choiceMode="multipleChoice"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
android:paddingBottom="@dimen/fab_list_padding"
|
|
||||||
tools:listitem="@layout/clear_database_source_item" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MaterialFastScroll
|
|
||||||
android:id="@+id/fast_scroller"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
app:fastScrollerBubbleEnabled="false"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.EmptyView
|
|
||||||
android:id="@+id/empty_view"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
@ -1,68 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:background="@drawable/list_item_selector_background"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="16dp">
|
|
||||||
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/thumbnail"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:paddingHorizontal="8dp"
|
|
||||||
app:layout_constraintDimensionRatio="1:1"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
tools:ignore="ContentDescription"
|
|
||||||
tools:src="@mipmap/ic_launcher" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/title"
|
|
||||||
style="?attr/textAppearanceBodyMedium"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="0dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:ellipsize="middle"
|
|
||||||
android:maxLines="1"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/thumbnail"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/checkbox"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/description"
|
|
||||||
app:layout_constraintVertical_chainStyle="packed"
|
|
||||||
tools:text="Source Name (LN)" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/description"
|
|
||||||
style="?attr/textAppearanceBodySmall"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
app:layout_constraintStart_toEndOf="@+id/thumbnail"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/checkbox"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/title"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
tools:text="999 non-library manga in database" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/checkbox"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:clickable="false"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:longClickable="false"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintDimensionRatio="1:2"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
Loading…
Reference in New Issue
Block a user