Handle offline mode in history list

pull/453/head
Koitharu 3 years ago
parent 6fa99791b6
commit 20a7e5a6a8
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -5,11 +5,15 @@ import coil.ImageLoader
import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.BaseListAdapter
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
class BookmarksAdapter( class BookmarksAdapter(
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
clickListener: OnListItemClickListener<Bookmark>, clickListener: OnListItemClickListener<Bookmark>,
) : BaseListAdapter<Bookmark>( ) : BaseListAdapter<Bookmark>() {
bookmarkListAD(coil, lifecycleOwner, clickListener),
) init {
addDelegate(ListItemType.PAGE_THUMB, bookmarkListAD(coil, lifecycleOwner, clickListener))
}
}

@ -0,0 +1,15 @@
package org.koitharu.kotatsu.core.os
import android.content.Intent
import android.os.Build
import android.provider.Settings
@Suppress("FunctionName")
fun NetworkManageIntent(): Intent {
val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Settings.Panel.ACTION_INTERNET_CONNECTIVITY
} else {
Settings.ACTION_WIRELESS_SETTINGS
}
return Intent(action)
}

@ -13,13 +13,10 @@ import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
open class BaseListAdapter<T : ListModel>( open class BaseListAdapter<T : ListModel> : AsyncListDifferDelegationAdapter<T>(
vararg delegates: AdapterDelegate<List<T>>,
) : AsyncListDifferDelegationAdapter<T>(
AsyncDifferConfig.Builder(ListModelDiffCallback<T>()) AsyncDifferConfig.Builder(ListModelDiffCallback<T>())
.setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor()) .setBackgroundThreadExecutor(Dispatchers.Default.limitedParallelism(2).asExecutor())
.build(), .build(),
*delegates,
), FlowCollector<List<T>?> { ), FlowCollector<List<T>?> {
override suspend fun emit(value: List<T>?) = suspendCoroutine { cont -> override suspend fun emit(value: List<T>?) = suspendCoroutine { cont ->

@ -53,6 +53,7 @@ import org.koitharu.kotatsu.details.ui.scrobbling.ScrollingInfoAdapter
import org.koitharu.kotatsu.history.data.PROGRESS_NONE import org.koitharu.kotatsu.history.data.PROGRESS_NONE
import org.koitharu.kotatsu.image.ui.ImageActivity import org.koitharu.kotatsu.image.ui.ImageActivity
import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel import org.koitharu.kotatsu.list.ui.model.MangaItemModel
@ -227,7 +228,9 @@ class DetailsFragment :
val rv = viewBinding?.recyclerViewRelated ?: return val rv = viewBinding?.recyclerViewRelated ?: return
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val adapter = (rv.adapter as? BaseListAdapter<ListModel>) ?: BaseListAdapter( val adapter = (rv.adapter as? BaseListAdapter<ListModel>) ?: BaseListAdapter<ListModel>()
.addDelegate(
ListItemType.MANGA_GRID,
mangaGridItemAD( mangaGridItemAD(
coil, viewLifecycleOwner, coil, viewLifecycleOwner,
StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)), StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)),

@ -7,6 +7,7 @@ import androidx.appcompat.view.ActionMode
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.os.NetworkManageIntent
import org.koitharu.kotatsu.core.ui.list.ListSelectionController import org.koitharu.kotatsu.core.ui.list.ListSelectionController
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.addMenuProvider
@ -32,6 +33,10 @@ class HistoryListFragment : MangaListFragment() {
override fun onScrolledToEnd() = Unit override fun onScrolledToEnd() = Unit
override fun onEmptyActionClick() {
startActivity(NetworkManageIntent())
}
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean { override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.mode_history, menu) mode.menuInflater.inflate(R.menu.mode_history, menu)
return super.onCreateActionMode(controller, mode, menu) return super.onCreateActionMode(controller, mode, menu)

@ -13,6 +13,8 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
@ -28,6 +30,7 @@ import org.koitharu.kotatsu.history.domain.model.HistoryOrder
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
import org.koitharu.kotatsu.list.domain.ListExtraProvider import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.MangaListViewModel import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
@ -36,6 +39,7 @@ import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toGridModel import org.koitharu.kotatsu.list.ui.model.toGridModel
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
import org.koitharu.kotatsu.list.ui.model.toListModel import org.koitharu.kotatsu.list.ui.model.toListModel
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import java.util.Date import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -45,6 +49,8 @@ class HistoryListViewModel @Inject constructor(
private val repository: HistoryRepository, private val repository: HistoryRepository,
private val settings: AppSettings, private val settings: AppSettings,
private val extraProvider: ListExtraProvider, private val extraProvider: ListExtraProvider,
private val localMangaRepository: LocalMangaRepository,
networkState: NetworkState,
downloadScheduler: DownloadWorker.Scheduler, downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler) { ) : MangaListViewModel(settings, downloadScheduler) {
@ -69,7 +75,8 @@ class HistoryListViewModel @Inject constructor(
sortOrder.flatMapLatest { repository.observeAllWithHistory(it) }, sortOrder.flatMapLatest { repository.observeAllWithHistory(it) },
isGroupingEnabled, isGroupingEnabled,
listMode, listMode,
) { list, grouped, mode -> networkState,
) { list, grouped, mode, online ->
when { when {
list.isEmpty() -> listOf( list.isEmpty() -> listOf(
EmptyState( EmptyState(
@ -80,7 +87,7 @@ class HistoryListViewModel @Inject constructor(
), ),
) )
else -> mapList(list, grouped, mode) else -> mapList(list, grouped, mode, online)
} }
}.onStart { }.onStart {
loadingCounter.increment() loadingCounter.increment()
@ -129,11 +136,25 @@ class HistoryListViewModel @Inject constructor(
list: List<MangaWithHistory>, list: List<MangaWithHistory>,
grouped: Boolean, grouped: Boolean,
mode: ListMode, mode: ListMode,
isOnline: Boolean,
): List<ListModel> { ): List<ListModel> {
val result = ArrayList<ListModel>(if (grouped) (list.size * 1.4).toInt() else list.size + 1) val result = ArrayList<ListModel>(if (grouped) (list.size * 1.4).toInt() else list.size + 1)
val order = sortOrder.value val order = sortOrder.value
var prevHeader: ListHeader? = null var prevHeader: ListHeader? = null
for ((manga, history) in list) { if (!isOnline) {
result += EmptyHint(
icon = R.drawable.ic_empty_common,
textPrimary = R.string.network_unavailable,
textSecondary = R.string.network_unavailable_hint,
actionStringRes = R.string.manage,
)
}
for ((m, history) in list) {
val manga = if (!isOnline && !m.isLocal) {
localMangaRepository.findSavedManga(m)?.manga ?: continue
} else {
m
}
if (grouped) { if (grouped) {
val header = history.header(order) val header = history.header(order)
if (header != prevHeader) { if (header != prevHeader) {

@ -37,7 +37,6 @@ import org.koitharu.kotatsu.core.util.ext.measureHeight
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
import org.koitharu.kotatsu.databinding.FragmentListBinding import org.koitharu.kotatsu.databinding.FragmentListBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
@ -200,7 +199,7 @@ abstract class MangaListFragment :
coil = coil, coil = coil,
lifecycleOwner = viewLifecycleOwner, lifecycleOwner = viewLifecycleOwner,
listener = this, listener = this,
sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = false) sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = false),
) )
} }

@ -22,6 +22,7 @@ open class MangaListAdapter(
addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener)) addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener))
addDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener)) addDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener))
addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener)) addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.HINT_EMPTY, emptyHintAD(coil, lifecycleOwner, listener))
addDelegate(ListItemType.HEADER, listHeaderAD(listener)) addDelegate(ListItemType.HEADER, listHeaderAD(listener))
} }
} }

@ -48,7 +48,7 @@ class TypedListSpacingDecoration(
null -> outRect.set(0) null -> outRect.set(0)
ListItemType.TIP -> outRect.set(0) // TODO ListItemType.TIP -> outRect.set(0) // TODO
ListItemType.HINT_EMPTY -> outRect.set(0) // TODO ListItemType.HINT_EMPTY -> outRect.set(spacingList)
ListItemType.FEED -> outRect.set(spacingList, 0, spacingList, 0) ListItemType.FEED -> outRect.set(spacingList, 0, spacingList, 0)
} }
} }

@ -129,6 +129,7 @@ class LocalMangaRepository @Inject constructor(
} }
suspend fun findSavedManga(remoteManga: Manga): LocalManga? { suspend fun findSavedManga(remoteManga: Manga): LocalManga? {
// TODO fast path by name
val files = getAllFiles() val files = getAllFiles()
return channelFlow { return channelFlow {
for (file in files) { for (file in files) {

@ -11,6 +11,9 @@ class SourcesSelectAdapter(
listener: SourceConfigListener, listener: SourceConfigListener,
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
) : BaseListAdapter<SourceConfigItem>( ) : BaseListAdapter<SourceConfigItem>() {
sourceConfigItemCheckableDelegate(listener, coil, lifecycleOwner),
) init {
delegatesManager.addDelegate(sourceConfigItemCheckableDelegate(listener, coil, lifecycleOwner))
}
}

@ -9,10 +9,15 @@ class SourceConfigAdapter(
listener: SourceConfigListener, listener: SourceConfigListener,
coil: ImageLoader, coil: ImageLoader,
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
) : BaseListAdapter<SourceConfigItem>( ) : BaseListAdapter<SourceConfigItem>() {
sourceConfigHeaderDelegate(),
sourceConfigGroupDelegate(listener), init {
sourceConfigItemDelegate2(listener, coil, lifecycleOwner), with(delegatesManager) {
sourceConfigEmptySearchDelegate(), addDelegate(sourceConfigHeaderDelegate())
sourceConfigTipDelegate(listener), addDelegate(sourceConfigGroupDelegate(listener))
) addDelegate(sourceConfigItemDelegate2(listener, coil, lifecycleOwner))
addDelegate(sourceConfigEmptySearchDelegate())
addDelegate(sourceConfigTipDelegate(listener))
}
}
}

@ -6,6 +6,9 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
class TrackerCategoriesConfigAdapter( class TrackerCategoriesConfigAdapter(
listener: OnListItemClickListener<FavouriteCategory>, listener: OnListItemClickListener<FavouriteCategory>,
) : BaseListAdapter<FavouriteCategory>( ) : BaseListAdapter<FavouriteCategory>() {
trackerCategoryAD(listener),
) init {
delegatesManager.addDelegate(trackerCategoryAD(listener))
}
}

@ -3,11 +3,9 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
style="@style/Widget.Material3.CardView.Filled" style="@style/Widget.Kotatsu.CardView.Light"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:layout_marginHorizontal="16dp"
app:contentPadding="@dimen/margin_normal"> app:contentPadding="@dimen/margin_normal">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout

Loading…
Cancel
Save