From c4b03d131600c31543d1d9776c5327d77aaefb54 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 6 Jul 2022 11:50:43 +0300 Subject: [PATCH] Library menu --- app/src/main/AndroidManifest.xml | 3 + .../dialog/RememberSelectionDialogListener.kt | 13 ++++ .../kotatsu/base/ui/util/ReversibleAction.kt | 9 +++ .../favourites/ui/FavouritesActivity.kt | 53 +++++++++++++++ .../kotatsu/history/data/HistoryDao.kt | 3 + .../history/domain/HistoryRepository.kt | 4 ++ .../kotatsu/history/ui/HistoryActivity.kt | 5 -- .../kotatsu/library/ui/LibraryFragment.kt | 18 ++++- .../kotatsu/library/ui/LibraryMenuProvider.kt | 65 +++++++++++++++++++ .../kotatsu/library/ui/LibraryViewModel.kt | 33 ++++++++++ .../koitharu/kotatsu/main/ui/MainActivity.kt | 1 + .../org/koitharu/kotatsu/utils/ext/DateExt.kt | 10 +++ .../color-v23/toolbar_background_scrim.xml | 4 ++ .../res/color/toolbar_background_scrim.xml | 2 +- .../drawable-v23/tab_rounded_rectangle.xml | 10 +++ .../drawable-v23/tab_selector_drawable.xml | 9 +++ .../res/drawable/tab_rounded_rectangle.xml | 2 +- .../res/drawable/tab_selector_drawable.xml | 2 +- app/src/main/res/layout/activity_details.xml | 4 +- app/src/main/res/menu/opt_library.xml | 19 ++++++ app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 1 + 22 files changed, 261 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RememberSelectionDialogListener.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/util/ReversibleAction.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesActivity.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt create mode 100644 app/src/main/res/color-v23/toolbar_background_scrim.xml create mode 100644 app/src/main/res/drawable-v23/tab_rounded_rectangle.xml create mode 100644 app/src/main/res/drawable-v23/tab_selector_drawable.xml create mode 100644 app/src/main/res/menu/opt_library.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4b0cf6180..d2e667908 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,9 @@ + () { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivityContainerBinding.inflate(layoutInflater)) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + val categoryTitle = intent.getStringExtra(EXTRA_TITLE) + if (categoryTitle != null) { + title = categoryTitle + } + val fm = supportFragmentManager + if (fm.findFragmentById(R.id.container) == null) { + fm.commit { + val fragment = FavouritesListFragment.newInstance(intent.getLongExtra(EXTRA_CATEGORY_ID, NO_ID)) + replace(R.id.container, fragment) + } + } + } + + override fun onWindowInsetsChanged(insets: Insets) { + binding.toolbar.updatePadding( + left = insets.left, + right = insets.right, + ) + } + + companion object { + + private const val EXTRA_CATEGORY_ID = "cat_id" + private const val EXTRA_TITLE = "title" + + fun newIntent(context: Context) = Intent(context, FavouritesActivity::class.java) + + fun newIntent(context: Context, category: FavouriteCategory) = Intent(context, FavouritesActivity::class.java) + .putExtra(EXTRA_CATEGORY_ID, category.id) + .putExtra(EXTRA_TITLE, category.title) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt index f27a0af0e..ce864a94d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -67,6 +67,9 @@ abstract class HistoryDao { @Query("DELETE FROM history WHERE manga_id = :mangaId") abstract suspend fun delete(mangaId: Long) + @Query("DELETE FROM history WHERE created_at >= :minDate") + abstract suspend fun deleteAfter(minDate: Long) + suspend fun update(entity: HistoryEntity) = update( mangaId = entity.mangaId, page = entity.page, diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 4b7bb1c99..8f574d37d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -115,6 +115,10 @@ class HistoryRepository( } } + suspend fun deleteAfter(minDate: Long) { + db.historyDao.delete(minDate) + } + suspend fun deleteReversible(ids: Collection): ReversibleHandle { val entities = db.withTransaction { val entities = db.historyDao.findAll(ids.toList()).filterNotNull() diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryActivity.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryActivity.kt index e8a9b71c7..198aac283 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryActivity.kt @@ -3,9 +3,7 @@ package org.koitharu.kotatsu.history.ui import android.content.Context import android.content.Intent import android.os.Bundle -import android.view.ViewGroup import androidx.core.graphics.Insets -import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.fragment.app.commit import org.koitharu.kotatsu.R @@ -32,9 +30,6 @@ class HistoryActivity : BaseActivity() { left = insets.left, right = insets.right, ) - binding.container.updatePadding( - bottom = insets.bottom - ) } companion object { diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt index 51c240a44..16025b130 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt @@ -10,12 +10,15 @@ import com.google.android.material.snackbar.Snackbar import org.koin.android.ext.android.get import org.koin.androidx.viewmodel.ext.android.viewModel import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.reverseAsync import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration +import org.koitharu.kotatsu.base.ui.util.ReversibleAction import org.koitharu.kotatsu.databinding.FragmentLibraryBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.ui.service.DownloadService +import org.koitharu.kotatsu.favourites.ui.FavouritesActivity import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.history.ui.HistoryActivity import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter @@ -27,6 +30,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.util.flattenTo import org.koitharu.kotatsu.utils.ShareHelper +import org.koitharu.kotatsu.utils.ext.addMenuProvider import org.koitharu.kotatsu.utils.ext.findViewsByType import org.koitharu.kotatsu.utils.ext.getDisplayMessage @@ -58,9 +62,11 @@ class LibraryFragment : BaseFragment(), LibraryListEvent ) binding.recyclerView.adapter = adapter binding.recyclerView.setHasFixedSize(true) + addMenuProvider(LibraryMenuProvider(view.context, viewModel)) viewModel.content.observe(viewLifecycleOwner, ::onListChanged) viewModel.onError.observe(viewLifecycleOwner, ::onError) + viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone) } override fun onDestroyView() { @@ -83,7 +89,7 @@ class LibraryFragment : BaseFragment(), LibraryListEvent override fun onSectionClick(section: LibrarySectionModel, view: View) { val intent = when (section) { is LibrarySectionModel.History -> HistoryActivity.newIntent(view.context) - is LibrarySectionModel.Favourites -> TODO() + is LibrarySectionModel.Favourites -> FavouritesActivity.newIntent(view.context, section.category) } startActivity(intent) } @@ -174,6 +180,16 @@ class LibraryFragment : BaseFragment(), LibraryListEvent ).show() } + private fun onActionDone(action: ReversibleAction) { + val handle = action.handle + val length = if (handle == null) Snackbar.LENGTH_SHORT else Snackbar.LENGTH_LONG + val snackbar = Snackbar.make(binding.recyclerView, action.stringResId, length) + if (handle != null) { + snackbar.setAction(R.string.undo) { handle.reverseAsync() } + } + snackbar.show() + } + companion object { fun newInstance() = LibraryFragment() diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt new file mode 100644 index 000000000..7a7354328 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryMenuProvider.kt @@ -0,0 +1,65 @@ +package org.koitharu.kotatsu.library.ui + +import android.content.Context +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import androidx.core.view.MenuProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.dialog.RememberSelectionDialogListener +import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity +import org.koitharu.kotatsu.utils.ext.startOfDay +import java.util.* +import java.util.concurrent.TimeUnit +import com.google.android.material.R as materialR + +class LibraryMenuProvider( + private val context: Context, + private val viewModel: LibraryViewModel, +) : MenuProvider { + + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + menuInflater.inflate(R.menu.opt_library, menu) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + return when (menuItem.itemId) { + R.id.action_clear_history -> { + showClearHistoryDialog() + true + } + R.id.action_categories -> { + context.startActivity(CategoriesActivity.newIntent(context)) + true + } + else -> false + } + } + + private fun showClearHistoryDialog() { + val selectionListener = RememberSelectionDialogListener(-1) + MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered) + .setTitle(R.string.clear_history) + .setSingleChoiceItems( + arrayOf( + context.getString(R.string.last_2_hours), + context.getString(R.string.today), + context.getString(R.string.clear_all_history), + ), + selectionListener.selection, + selectionListener, + ) + .setIcon(R.drawable.ic_delete) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.clear) { _, _ -> + val minDate = when (selectionListener.selection) { + 0 -> System.currentTimeMillis() - TimeUnit.HOURS.toMillis(2) + 1 -> Date().startOfDay() + 2 -> 0L + else -> return@setPositiveButton + } + viewModel.clearHistory(minDate) + }.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt index 548f4d804..ff1c49f45 100644 --- a/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt @@ -7,7 +7,10 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.ReversibleHandle +import org.koitharu.kotatsu.base.domain.plus import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.base.ui.util.ReversibleAction import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.os.ShortcutsRepository import org.koitharu.kotatsu.core.prefs.AppSettings @@ -23,6 +26,7 @@ import org.koitharu.kotatsu.list.ui.model.* import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.tracker.domain.TrackingRepository +import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct import org.koitharu.kotatsu.utils.ext.daysDiff import java.util.* @@ -37,6 +41,8 @@ class LibraryViewModel( private val settings: AppSettings, ) : BaseViewModel(), ListExtraProvider { + val onActionDone = SingleLiveEvent() + val content: LiveData> = combine( historyRepository.observeAllWithHistory(), favouritesRepository.observeAllGrouped(SortOrder.NEWEST), @@ -77,6 +83,33 @@ class LibraryViewModel( return result } + fun removeFromHistory(ids: Set) { + if (ids.isEmpty()) { + return + } + launchJob(Dispatchers.Default) { + val handle = historyRepository.deleteReversible(ids) + ReversibleHandle { + shortcutsRepository.updateShortcuts() + } + shortcutsRepository.updateShortcuts() + onActionDone.postCall(ReversibleAction(R.string.removed_from_history, handle)) + } + } + + fun clearHistory(minDate: Long) { + launchJob(Dispatchers.Default) { + val stringRes = if (minDate <= 0) { + historyRepository.clear() + R.string.history_cleared + } else { + historyRepository.deleteAfter(minDate) + R.string.removed_from_history + } + shortcutsRepository.updateShortcuts() + onActionDone.postCall(ReversibleAction(stringRes, null)) + } + } + private suspend fun mapList( history: List, favourites: Map>, diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt index 62327bb79..f735f05f4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -301,6 +301,7 @@ class MainActivity : } private fun onSearchClosed() { + binding.searchView.hideKeyboard() TransitionManager.beginDelayedTransition(binding.appbar) binding.toolbarCard.updateLayoutParams { scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/DateExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/DateExt.kt index 0cc08fd55..0a78f0341 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/DateExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/DateExt.kt @@ -17,4 +17,14 @@ fun Date.daysDiff(other: Long): Int { val thisDay = time / TimeUnit.DAYS.toMillis(1L) val otherDay = other / TimeUnit.DAYS.toMillis(1L) return (thisDay - otherDay).toInt() +} + +fun Date.startOfDay(): Long { + val calendar = Calendar.getInstance() + calendar.time = this + calendar[Calendar.HOUR_OF_DAY] = 0 + calendar[Calendar.MINUTE] = 0 + calendar[Calendar.SECOND] = 0 + calendar[Calendar.MILLISECOND] = 0 + return calendar.timeInMillis } \ No newline at end of file diff --git a/app/src/main/res/color-v23/toolbar_background_scrim.xml b/app/src/main/res/color-v23/toolbar_background_scrim.xml new file mode 100644 index 000000000..1c0adc72a --- /dev/null +++ b/app/src/main/res/color-v23/toolbar_background_scrim.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/color/toolbar_background_scrim.xml b/app/src/main/res/color/toolbar_background_scrim.xml index 1c0adc72a..58c5c9484 100644 --- a/app/src/main/res/color/toolbar_background_scrim.xml +++ b/app/src/main/res/color/toolbar_background_scrim.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable-v23/tab_rounded_rectangle.xml b/app/src/main/res/drawable-v23/tab_rounded_rectangle.xml new file mode 100644 index 000000000..724a112c6 --- /dev/null +++ b/app/src/main/res/drawable-v23/tab_rounded_rectangle.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v23/tab_selector_drawable.xml b/app/src/main/res/drawable-v23/tab_selector_drawable.xml new file mode 100644 index 000000000..3ef15feb1 --- /dev/null +++ b/app/src/main/res/drawable-v23/tab_selector_drawable.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_rounded_rectangle.xml b/app/src/main/res/drawable/tab_rounded_rectangle.xml index 724a112c6..78b0e7813 100644 --- a/app/src/main/res/drawable/tab_rounded_rectangle.xml +++ b/app/src/main/res/drawable/tab_rounded_rectangle.xml @@ -4,7 +4,7 @@ + android:color="@color/kotatsu_secondary" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_selector_drawable.xml b/app/src/main/res/drawable/tab_selector_drawable.xml index 3ef15feb1..7748f1d8f 100644 --- a/app/src/main/res/drawable/tab_selector_drawable.xml +++ b/app/src/main/res/drawable/tab_selector_drawable.xml @@ -2,7 +2,7 @@ diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml index 742cdf3f8..56b04b77e 100644 --- a/app/src/main/res/layout/activity_details.xml +++ b/app/src/main/res/layout/activity_details.xml @@ -29,9 +29,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_horizontal" - android:background="@null" - app:tabGravity="center" - app:tabMode="scrollable" /> + android:background="@null" /> diff --git a/app/src/main/res/menu/opt_library.xml b/app/src/main/res/menu/opt_library.xml new file mode 100644 index 000000000..a8f3ed456 --- /dev/null +++ b/app/src/main/res/menu/opt_library.xml @@ -0,0 +1,19 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6a4dfc4c5..db7037d18 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -320,4 +320,7 @@ Manga marked as NSFW will never added to the history and your progress will not be saved Can help in case of some issues. All authorizations will be invalidated Show all + Clear all history + Last 2 hours + History cleared \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index de5c7db40..ad32cf5bc 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -72,6 +72,7 @@ 8dp 8dp 0dp + false