Library menu
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
4
app/src/main/res/color-v23/toolbar_background_scrim.xml
Normal file
4
app/src/main/res/color-v23/toolbar_background_scrim.xml
Normal 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>
|
||||
@@ -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>
|
||||
10
app/src/main/res/drawable-v23/tab_rounded_rectangle.xml
Normal file
10
app/src/main/res/drawable-v23/tab_rounded_rectangle.xml
Normal 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>
|
||||
9
app/src/main/res/drawable-v23/tab_selector_drawable.xml
Normal file
9
app/src/main/res/drawable-v23/tab_selector_drawable.xml
Normal 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>
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<corners android:radius="6dp" />
|
||||
<solid
|
||||
android:color="?attr/colorAccent" />
|
||||
android:color="@color/kotatsu_secondary" />
|
||||
<size android:height="32dp" />
|
||||
|
||||
</shape>
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
19
app/src/main/res/menu/opt_library.xml
Normal file
19
app/src/main/res/menu/opt_library.xml
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user