Option to group history by date

This commit is contained in:
Koitharu
2020-11-28 14:02:03 +02:00
parent b1be45af8b
commit 9c20559962
18 changed files with 219 additions and 20 deletions

View File

@@ -72,6 +72,8 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
val isPreferRtlReader by BoolPreferenceDelegate(KEY_READER_PREFER_RTL, false)
var historyGrouping by BoolPreferenceDelegate(KEY_HISTORY_GROUPING, true)
val zoomMode by EnumPreferenceDelegate(
ZoomMode::class.java,
KEY_ZOOM_MODE,
@@ -169,5 +171,6 @@ class AppSettings private constructor(private val prefs: SharedPreferences) :
const val KEY_ZOOM_MODE = "zoom_mode"
const val KEY_BACKUP = "backup"
const val KEY_RESTORE = "restore"
const val KEY_HISTORY_GROUPING = "history_grouping"
}
}

View File

@@ -0,0 +1,66 @@
package org.koitharu.kotatsu.core.ui
import android.content.res.Resources
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.ext.daysDiff
import java.util.*
import java.util.concurrent.TimeUnit
sealed class DateTimeAgo {
abstract fun format(resources: Resources): String
object JustNow : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getString(R.string.just_now)
}
}
data class MinutesAgo(val minutes: Int) : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getQuantityString(R.plurals.minutes_ago, minutes, minutes)
}
}
data class HoursAgo(val hours: Int) : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getQuantityString(R.plurals.hours_ago, hours, hours)
}
}
object Yesterday : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getString(R.string.yesterday)
}
}
data class DaysAgo(val days: Int) : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getQuantityString(R.plurals.days_ago, days, days)
}
}
object LongAgo : DateTimeAgo() {
override fun format(resources: Resources): String {
return resources.getString(R.string.long_ago)
}
}
companion object {
fun from(date: Date): DateTimeAgo {
val diff = (System.currentTimeMillis() - date.time).coerceAtLeast(0L)
val diffMinutes = TimeUnit.MILLISECONDS.toMinutes(diff).toInt()
val diffHours = TimeUnit.MILLISECONDS.toHours(diff).toInt()
val diffDays = -date.daysDiff(System.currentTimeMillis())
return when {
diffMinutes < 1 -> JustNow
diffMinutes < 60 -> MinutesAgo(diffMinutes)
diffDays < 1 -> HoursAgo(diffHours)
diffDays == 1 -> Yesterday
diffDays < 16 -> DaysAgo(diffDays)
else -> LongAgo
}
}
}
}

View File

@@ -33,6 +33,15 @@ class HistoryRepository(private val db: MangaDatabase) : KoinComponent {
}
}
fun observeAllWithHistory(): Flow<List<MangaWithHistory>> {
return db.historyDao.observeAll().mapItems {
MangaWithHistory(
it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)),
it.history.toMangaHistory()
)
}
}
fun observeOne(id: Long): Flow<MangaHistory?> {
return db.historyDao.observe(id).map {
it?.toMangaHistory()

View File

@@ -0,0 +1,9 @@
package org.koitharu.kotatsu.history.domain
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaHistory
data class MangaWithHistory(
val manga: Manga,
val history: MangaHistory
)

View File

@@ -22,6 +22,9 @@ class HistoryListFragment : MangaListFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.onItemRemoved.observe(viewLifecycleOwner, ::onItemRemoved)
viewModel.isGroupingEnabled.observe(viewLifecycleOwner) {
activity?.invalidateOptionsMenu()
}
}
override fun onScrolledToEnd() = Unit
@@ -31,6 +34,11 @@ class HistoryListFragment : MangaListFragment() {
super.onCreateOptionsMenu(menu, inflater)
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
menu.findItem(R.id.action_history_grouping)?.isChecked = viewModel.isGroupingEnabled.value == true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_clear_history -> {
@@ -43,12 +51,16 @@ class HistoryListFragment : MangaListFragment() {
}.show()
true
}
R.id.action_history_grouping -> {
viewModel.setGrouping(!item.isChecked)
true
}
else -> super.onOptionsItemSelected(item)
}
}
override fun getTitle(): CharSequence? {
return getString(R.string.history)
return context?.getString(R.string.history)
}
override fun setUpEmptyListHolder() {
@@ -71,7 +83,7 @@ class HistoryListFragment : MangaListFragment() {
}
}
fun onItemRemoved(item: Manga) {
private fun onItemRemoved(item: Manga) {
Snackbar.make(
recyclerView, getString(
R.string._s_removed_from_history,

View File

@@ -2,16 +2,17 @@ package org.koitharu.kotatsu.history.ui
import android.content.Context
import android.os.Build
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.*
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.toGridModel
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
@@ -22,22 +23,26 @@ import org.koitharu.kotatsu.utils.ext.onFirst
class HistoryListViewModel(
private val repository: HistoryRepository,
private val context: Context //todo create ShortcutRepository
, settings: AppSettings
private val context: Context, //todo create ShortcutRepository
private val settings: AppSettings
) : MangaListViewModel(settings) {
val onItemRemoved = SingleLiveEvent<Manga>()
val isGroupingEnabled = MutableLiveData<Boolean>()
private val historyGrouping = settings.observe()
.filter { it == AppSettings.KEY_HISTORY_GROUPING }
.map { settings.historyGrouping }
.onStart { emit(settings.historyGrouping) }
.distinctUntilChanged()
.onEach { isGroupingEnabled.postValue(it) }
override val content = combine(
repository.observeAll(),
createListModeFlow()
) { list, mode ->
when (mode) {
ListMode.LIST -> list.map { it.toListModel() }
ListMode.DETAILED_LIST -> list.map { it.toListDetailedModel() }
ListMode.GRID -> list.map { it.toGridModel() }
}
}.onEach {
repository.observeAllWithHistory(),
historyGrouping,
createListModeFlow(),
::mapList
).onEach {
isEmptyState.postValue(it.isEmpty())
}.onStart {
isLoading.postValue(true)
@@ -64,4 +69,27 @@ class HistoryListViewModel(
}
}
fun setGrouping(isGroupingEnabled: Boolean) {
settings.historyGrouping = isGroupingEnabled
}
private fun mapList(list: List<MangaWithHistory>, grouped: Boolean, mode: ListMode): List<Any> {
val result = ArrayList<Any>((list.size * 1.4).toInt())
var prevDate: DateTimeAgo? = null
for ((manga, history) in list) {
if (grouped) {
val date = DateTimeAgo.from(history.updatedAt)
if (prevDate != date) {
result += date
}
prevDate = date
}
result += when (mode) {
ListMode.LIST -> manga.toListModel()
ListMode.DETAILED_LIST -> manga.toListDetailedModel()
ListMode.GRID -> manga.toGridModel()
}
}
return result
}
}

View File

@@ -273,6 +273,7 @@ abstract class MangaListFragment : BaseFragment(R.layout.fragment_list),
override fun getSpanSize(position: Int): Int {
val total = (recyclerView.layoutManager as? GridLayoutManager)?.spanCount ?: return 1
return when (adapter?.getItemViewType(position)) {
MangaListAdapter.ITEM_TYPE_DATE,
MangaListAdapter.ITEM_TYPE_PROGRESS -> total
else -> 1
}

View File

@@ -5,6 +5,7 @@ import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.IndeterminateProgress
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
@@ -24,6 +25,7 @@ class MangaListAdapter(
)
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, clickListener))
.addDelegate(ITEM_TYPE_PROGRESS, indeterminateProgressAD())
.addDelegate(ITEM_TYPE_DATE, relatedDateItemAD())
}
private class DiffCallback : DiffUtil.ItemCallback<Any>() {
@@ -41,6 +43,9 @@ class MangaListAdapter(
oldItem == IndeterminateProgress && newItem == IndeterminateProgress -> {
true
}
oldItem is DateTimeAgo && newItem is DateTimeAgo -> {
oldItem == newItem
}
else -> false
}
@@ -55,5 +60,6 @@ class MangaListAdapter(
const val ITEM_TYPE_MANGA_LIST_DETAILED = 1
const val ITEM_TYPE_MANGA_GRID = 2
const val ITEM_TYPE_PROGRESS = 3
const val ITEM_TYPE_DATE = 4
}
}

View File

@@ -0,0 +1,13 @@
package org.koitharu.kotatsu.list.ui.adapter
import android.widget.TextView
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.DateTimeAgo
fun relatedDateItemAD() = adapterDelegate<DateTimeAgo, Any>(R.layout.item_header) {
bind {
(itemView as TextView).text = item.format(context.resources)
}
}

View File

@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.text.format.DateUtils
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.TimeUnit
@SuppressLint("SimpleDateFormat")
fun Date.format(pattern: String): String = SimpleDateFormat(pattern).format(this)
@@ -14,4 +15,10 @@ fun Date.calendar(): Calendar = Calendar.getInstance().also {
fun Date.formatRelative(minResolution: Long): CharSequence = DateUtils.getRelativeTimeSpanString(
time, System.currentTimeMillis(), minResolution
)
)
fun Date.daysDiff(other: Long): Int {
val thisDay = time / TimeUnit.DAYS.toMillis(1L)
val otherDay = other/ TimeUnit.DAYS.toMillis(1L)
return (thisDay - otherDay).toInt()
}