mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-30 22:07:57 +01:00 
			
		
		
		
	Use Compose on Clear Database screen (#7639)
This commit is contained in:
		| @@ -2,12 +2,12 @@ package eu.kanade.data.source | ||||
|  | ||||
| import eu.kanade.data.DatabaseHandler | ||||
| import eu.kanade.domain.source.model.Source | ||||
| import eu.kanade.domain.source.model.SourceWithCount | ||||
| import eu.kanade.domain.source.repository.SourceRepository | ||||
| import eu.kanade.tachiyomi.source.LocalSource | ||||
| import eu.kanade.tachiyomi.source.SourceManager | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import kotlinx.coroutines.flow.map | ||||
| import eu.kanade.tachiyomi.source.Source as LoadedSource | ||||
|  | ||||
| class SourceRepositoryImpl( | ||||
|     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() } | ||||
|         return sourceIdWithNonLibraryManga.map { sourceId -> | ||||
|             sourceId.map { (sourceId, count) -> | ||||
|                 val source = sourceManager.getOrStub(sourceId) | ||||
|                 source to count | ||||
|                 SourceWithCount(sourceMapper(source), count) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| package eu.kanade.domain.source.interactor | ||||
|  | ||||
| import eu.kanade.domain.source.model.SourceWithCount | ||||
| import eu.kanade.domain.source.repository.SourceRepository | ||||
| import eu.kanade.tachiyomi.source.Source | ||||
| import kotlinx.coroutines.flow.Flow | ||||
|  | ||||
| class GetSourcesWithNonLibraryManga( | ||||
|     private val repository: SourceRepository, | ||||
| ) { | ||||
|  | ||||
|     fun subscribe(): Flow<List<Pair<Source, Long>>> { | ||||
|     fun subscribe(): Flow<List<SourceWithCount>> { | ||||
|         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 | ||||
|  | ||||
| import eu.kanade.domain.source.model.Source | ||||
| import eu.kanade.domain.source.model.SourceWithCount | ||||
| import kotlinx.coroutines.flow.Flow | ||||
| import eu.kanade.tachiyomi.source.Source as LoadedSource | ||||
|  | ||||
| interface SourceRepository { | ||||
|  | ||||
| @@ -12,5 +12,5 @@ interface SourceRepository { | ||||
|  | ||||
|     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 | ||||
|  | ||||
| import android.annotation.SuppressLint | ||||
| import android.app.Dialog | ||||
| import android.os.Bundle | ||||
| 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 | ||||
| import androidx.compose.runtime.Composable | ||||
| import eu.kanade.presentation.more.settings.database.ClearDatabaseScreen | ||||
| import eu.kanade.tachiyomi.ui.base.controller.FullComposeController | ||||
|  | ||||
| class ClearDatabaseController : | ||||
|     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) | ||||
|     } | ||||
| class ClearDatabaseController : FullComposeController<ClearDatabasePresenter>() { | ||||
|  | ||||
|     override fun createPresenter(): ClearDatabasePresenter { | ||||
|         return ClearDatabasePresenter() | ||||
|     } | ||||
|  | ||||
|     override fun getTitle(): String? { | ||||
|         return activity?.getString(R.string.pref_clear_database) | ||||
|     } | ||||
|  | ||||
|     override fun onViewCreated(view: View) { | ||||
|         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) | ||||
|     @Composable | ||||
|     override fun ComposeContent() { | ||||
|         ClearDatabaseScreen( | ||||
|             presenter = presenter, | ||||
|             navigateUp = { router.popCurrentController() }, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,18 +2,21 @@ package eu.kanade.tachiyomi.ui.setting.database | ||||
|  | ||||
| import android.os.Bundle | ||||
| 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.ui.base.presenter.BasePresenter | ||||
| import eu.kanade.tachiyomi.util.lang.launchIO | ||||
| import eu.kanade.tachiyomi.util.lang.withUIContext | ||||
| import kotlinx.coroutines.flow.collectLatest | ||||
| import uy.kohesive.injekt.Injekt | ||||
| import uy.kohesive.injekt.api.get | ||||
|  | ||||
| class ClearDatabasePresenter( | ||||
|     private val state: ClearDatabaseStateImpl = ClearDatabaseState() as ClearDatabaseStateImpl, | ||||
|     private val database: Database = Injekt.get(), | ||||
|     private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get(), | ||||
| ) : BasePresenter<ClearDatabaseController>() { | ||||
| ) : BasePresenter<ClearDatabaseController>(), ClearDatabaseState by state { | ||||
|  | ||||
|     override fun onCreate(savedState: Bundle?) { | ||||
|         super.onCreate(savedState) | ||||
| @@ -21,17 +24,39 @@ class ClearDatabasePresenter( | ||||
|         presenterScope.launchIO { | ||||
|             getSourcesWithNonLibraryManga.subscribe() | ||||
|                 .collectLatest { list -> | ||||
|                     val items = list | ||||
|                         .map { (source, count) -> ClearDatabaseSourceItem(source, count) } | ||||
|                         .sortedBy { it.source.name } | ||||
|  | ||||
|                     withUIContext { view?.setItems(items) } | ||||
|                     state.items = list.sortedBy { it.name } | ||||
|                 } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun clearDatabaseForSourceIds(sources: List<Long>) { | ||||
|         database.mangasQueries.deleteMangasNotInLibraryBySourceIds(sources) | ||||
|     fun removeMangaBySourceId(sourceIds: List<Long>) { | ||||
|         database.mangasQueries.deleteMangasNotInLibraryBySourceIds(sourceIds) | ||||
|         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> | ||||
		Reference in New Issue
	
	Block a user