Library menu

pull/163/head
Koitharu 4 years ago
parent 5d54298a22
commit c4b03d1316
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -60,6 +60,9 @@
<activity <activity
android:name=".history.ui.HistoryActivity" android:name=".history.ui.HistoryActivity"
android:label="@string/history" /> android:label="@string/history" />
<activity
android:name=".favourites.ui.FavouritesActivity"
android:label="@string/favourites" />
<activity <activity
android:name="org.koitharu.kotatsu.settings.SettingsActivity" android:name="org.koitharu.kotatsu.settings.SettingsActivity"
android:exported="true" 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") @Query("DELETE FROM history WHERE manga_id = :mangaId")
abstract suspend fun delete(mangaId: Long) 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( suspend fun update(entity: HistoryEntity) = update(
mangaId = entity.mangaId, mangaId = entity.mangaId,
page = entity.page, 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 { suspend fun deleteReversible(ids: Collection<Long>): ReversibleHandle {
val entities = db.withTransaction { val entities = db.withTransaction {
val entities = db.historyDao.findAll(ids.toList()).filterNotNull() 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.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.commit import androidx.fragment.app.commit
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -32,9 +30,6 @@ class HistoryActivity : BaseActivity<ActivityContainerBinding>() {
left = insets.left, left = insets.left,
right = insets.right, right = insets.right,
) )
binding.container.updatePadding(
bottom = insets.bottom
)
} }
companion object { companion object {

@ -10,12 +10,15 @@ import com.google.android.material.snackbar.Snackbar
import org.koin.android.ext.android.get import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R 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.BaseFragment
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration 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.databinding.FragmentLibraryBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.ui.service.DownloadService 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.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
import org.koitharu.kotatsu.history.ui.HistoryActivity import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter 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.model.Manga
import org.koitharu.kotatsu.parsers.util.flattenTo import org.koitharu.kotatsu.parsers.util.flattenTo
import org.koitharu.kotatsu.utils.ShareHelper 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.findViewsByType
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
@ -58,9 +62,11 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), LibraryListEvent
) )
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
addMenuProvider(LibraryMenuProvider(view.context, viewModel))
viewModel.content.observe(viewLifecycleOwner, ::onListChanged) viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError) viewModel.onError.observe(viewLifecycleOwner, ::onError)
viewModel.onActionDone.observe(viewLifecycleOwner, ::onActionDone)
} }
override fun onDestroyView() { override fun onDestroyView() {
@ -83,7 +89,7 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), LibraryListEvent
override fun onSectionClick(section: LibrarySectionModel, view: View) { override fun onSectionClick(section: LibrarySectionModel, view: View) {
val intent = when (section) { val intent = when (section) {
is LibrarySectionModel.History -> HistoryActivity.newIntent(view.context) is LibrarySectionModel.History -> HistoryActivity.newIntent(view.context)
is LibrarySectionModel.Favourites -> TODO() is LibrarySectionModel.Favourites -> FavouritesActivity.newIntent(view.context, section.category)
} }
startActivity(intent) startActivity(intent)
} }
@ -174,6 +180,16 @@ class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), LibraryListEvent
).show() ).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 { companion object {
fun newInstance() = LibraryFragment() 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.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R 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.BaseViewModel
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.os.ShortcutsRepository import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.core.prefs.AppSettings 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.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.tracker.domain.TrackingRepository 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.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.daysDiff import org.koitharu.kotatsu.utils.ext.daysDiff
import java.util.* import java.util.*
@ -37,6 +41,8 @@ class LibraryViewModel(
private val settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel(), ListExtraProvider { ) : BaseViewModel(), ListExtraProvider {
val onActionDone = SingleLiveEvent<ReversibleAction>()
val content: LiveData<List<ListModel>> = combine( val content: LiveData<List<ListModel>> = combine(
historyRepository.observeAllWithHistory(), historyRepository.observeAllWithHistory(),
favouritesRepository.observeAllGrouped(SortOrder.NEWEST), favouritesRepository.observeAllGrouped(SortOrder.NEWEST),
@ -77,6 +83,33 @@ class LibraryViewModel(
return result 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( private suspend fun mapList(
history: List<MangaWithHistory>, history: List<MangaWithHistory>,
favourites: Map<FavouriteCategory, List<Manga>>, favourites: Map<FavouriteCategory, List<Manga>>,

@ -301,6 +301,7 @@ class MainActivity :
} }
private fun onSearchClosed() { private fun onSearchClosed() {
binding.searchView.hideKeyboard()
TransitionManager.beginDelayedTransition(binding.appbar) TransitionManager.beginDelayedTransition(binding.appbar)
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS or SCROLL_FLAG_SNAP

@ -18,3 +18,13 @@ fun Date.daysDiff(other: Long): Int {
val otherDay = other / TimeUnit.DAYS.toMillis(1L) val otherDay = other / TimeUnit.DAYS.toMillis(1L)
return (thisDay - otherDay).toInt() 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
}

@ -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"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <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> </selector>

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

@ -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" /> <corners android:radius="6dp" />
<solid <solid
android:color="?attr/colorAccent" /> android:color="@color/kotatsu_secondary" />
<size android:height="32dp" /> <size android:height="32dp" />
</shape> </shape>

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

@ -29,9 +29,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:background="@null" android:background="@null" />
app:tabGravity="center"
app:tabMode="scrollable" />
</com.google.android.material.appbar.MaterialToolbar> </com.google.android.material.appbar.MaterialToolbar>

@ -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="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="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="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> </resources>

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

Loading…
Cancel
Save