Library menu

This commit is contained in:
Koitharu
2022-07-06 11:50:43 +03:00
parent 5d54298a22
commit c4b03d1316
22 changed files with 261 additions and 12 deletions

View File

@@ -60,6 +60,9 @@
<activity
android:name=".history.ui.HistoryActivity"
android:label="@string/history" />
<activity
android:name=".favourites.ui.FavouritesActivity"
android:label="@string/favourites" />
<activity
android:name="org.koitharu.kotatsu.settings.SettingsActivity"
android:exported="true"

View File

@@ -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
}
}

View File

@@ -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?,
)

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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()

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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()
}
}

View File

@@ -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>>,

View File

@@ -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

View File

@@ -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
}

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.7" android:color="?attr/colorSecondaryContainer" />
</selector>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.7" android:color="?attr/colorSecondaryContainer" />
<item android:alpha="0.7" android:color="@color/kotatsu_secondaryContainer" />
</selector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="6dp" />
<solid
android:color="?attr/colorAccent" />
<size android:height="32dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:color="?attr/colorAccent"
android:drawable="@drawable/tab_rounded_rectangle"
android:padding="1dp" />
</selector>

View File

@@ -4,7 +4,7 @@
<corners android:radius="6dp" />
<solid
android:color="?attr/colorAccent" />
android:color="@color/kotatsu_secondary" />
<size android:height="32dp" />
</shape>

View File

@@ -2,7 +2,7 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:color="?attr/colorAccent"
android:color="@color/kotatsu_secondary"
android:drawable="@drawable/tab_rounded_rectangle"
android:padding="1dp" />

View File

@@ -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" />
</com.google.android.material.appbar.MaterialToolbar>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_categories"
android:orderInCategory="50"
android:title="@string/categories_"
app:showAsAction="never" />
<item
android:id="@+id/action_clear_history"
android:orderInCategory="50"
android:title="@string/clear_history"
app:showAsAction="never" />
</menu>

View File

@@ -320,4 +320,7 @@
<string name="exclude_nsfw_from_history_summary">Manga marked as NSFW will never added to the history and your progress will not be saved</string>
<string name="clear_cookies_summary">Can help in case of some issues. All authorizations will be invalidated</string>
<string name="show_all">Show all</string>
<string name="clear_all_history">Clear all history</string>
<string name="last_2_hours">Last 2 hours</string>
<string name="history_cleared">History cleared</string>
</resources>

View File

@@ -72,6 +72,7 @@
<item name="tabPaddingStart">8dp</item>
<item name="tabPaddingEnd">8dp</item>
<item name="dividerThickness">0dp</item>
<item name="android:clipChildren">false</item>
</style>
<style name="Widget.Kotatsu.SearchView" parent="@style/Widget.AppCompat.SearchView">