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