Library menu
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package org.koitharu.kotatsu.base.ui.dialog
|
||||
|
||||
import android.content.DialogInterface
|
||||
|
||||
class RememberSelectionDialogListener(initialValue: Int) : DialogInterface.OnClickListener {
|
||||
|
||||
var selection: Int = initialValue
|
||||
private set
|
||||
|
||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||
selection = which
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.koitharu.kotatsu.base.ui.util
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import org.koitharu.kotatsu.base.domain.ReversibleHandle
|
||||
|
||||
class ReversibleAction(
|
||||
@StringRes val stringResId: Int,
|
||||
val handle: ReversibleHandle?,
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
package org.koitharu.kotatsu.favourites.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.commit
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
|
||||
class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -115,6 +115,10 @@ class HistoryRepository(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteAfter(minDate: Long) {
|
||||
db.historyDao.delete(minDate)
|
||||
}
|
||||
|
||||
suspend fun deleteReversible(ids: Collection<Long>): ReversibleHandle {
|
||||
val entities = db.withTransaction {
|
||||
val entities = db.historyDao.findAll(ids.toList()).filterNotNull()
|
||||
|
||||
@@ -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<ActivityContainerBinding>() {
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
binding.container.updatePadding(
|
||||
bottom = insets.bottom
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -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<FragmentLibraryBinding>(), 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<FragmentLibraryBinding>(), 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<FragmentLibraryBinding>(), 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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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<ReversibleAction>()
|
||||
|
||||
val content: LiveData<List<ListModel>> = combine(
|
||||
historyRepository.observeAllWithHistory(),
|
||||
favouritesRepository.observeAllGrouped(SortOrder.NEWEST),
|
||||
@@ -77,6 +83,33 @@ class LibraryViewModel(
|
||||
return result
|
||||
}
|
||||
|
||||
fun removeFromHistory(ids: Set<Long>) {
|
||||
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<MangaWithHistory>,
|
||||
favourites: Map<FavouriteCategory, List<Manga>>,
|
||||
|
||||
@@ -301,6 +301,7 @@ class MainActivity :
|
||||
}
|
||||
|
||||
private fun onSearchClosed() {
|
||||
binding.searchView.hideKeyboard()
|
||||
TransitionManager.beginDelayedTransition(binding.appbar)
|
||||
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
|
||||
scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user