Add library fragment

This commit is contained in:
Koitharu
2022-07-04 09:38:39 +03:00
parent 0eff85dca3
commit ebdc2dfb0e
17 changed files with 510 additions and 97 deletions

View File

@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.core.ui.uiModule
import org.koitharu.kotatsu.details.detailsModule import org.koitharu.kotatsu.details.detailsModule
import org.koitharu.kotatsu.favourites.favouritesModule import org.koitharu.kotatsu.favourites.favouritesModule
import org.koitharu.kotatsu.history.historyModule import org.koitharu.kotatsu.history.historyModule
import org.koitharu.kotatsu.library.libraryModule
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.local.localModule import org.koitharu.kotatsu.local.localModule
@@ -77,6 +78,7 @@ class KotatsuApp : Application() {
suggestionsModule, suggestionsModule,
shikimoriModule, shikimoriModule,
bookmarksModule, bookmarksModule,
libraryModule,
) )
} }
} }

View File

@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.db.entity.*
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.FavouriteEntity import org.koitharu.kotatsu.favourites.data.FavouriteEntity
import org.koitharu.kotatsu.favourites.data.FavouriteManga
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
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
@@ -28,6 +29,11 @@ class FavouritesRepository(
.mapItems { it.manga.toManga(it.tags.toMangaTags()) } .mapItems { it.manga.toManga(it.tags.toMangaTags()) }
} }
fun observeAllGrouped(order: SortOrder): Flow<Map<FavouriteCategory, List<Manga>>> {
return db.favouritesDao.observeAll(order)
.map { list -> groupByCategory(list) }
}
suspend fun getManga(categoryId: Long): List<Manga> { suspend fun getManga(categoryId: Long): List<Manga> {
val entities = db.favouritesDao.findAll(categoryId) val entities = db.favouritesDao.findAll(categoryId)
return entities.map { it.manga.toManga(it.tags.toMangaTags()) } return entities.map { it.manga.toManga(it.tags.toMangaTags()) }
@@ -163,4 +169,16 @@ class FavouritesRepository(
.map { x -> SortOrder(x.order, SortOrder.NEWEST) } .map { x -> SortOrder(x.order, SortOrder.NEWEST) }
.distinctUntilChanged() .distinctUntilChanged()
} }
private fun groupByCategory(list: List<FavouriteManga>): Map<FavouriteCategory, List<Manga>> {
val map = HashMap<FavouriteCategory, MutableList<Manga>>()
for (item in list) {
val manga = item.manga.toManga(item.tags.toMangaTags())
for (category in item.categories) {
map.getOrPut(category.toFavouriteCategory()) { ArrayList() }
.add(manga)
}
}
return map
}
} }

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.library
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
import org.koitharu.kotatsu.library.ui.LibraryViewModel
val libraryModule
get() = module {
viewModel { LibraryViewModel(get(), get(), get(), get(), get()) }
}

View File

@@ -0,0 +1,186 @@
package org.koitharu.kotatsu.library.ui
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
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.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
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.categories.select.FavouriteCategoriesBottomSheet
import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.library.ui.adapter.LibraryAdapter
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.findViewsByType
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class LibraryFragment : BaseFragment<FragmentLibraryBinding>(), MangaListListener, ActionMode.Callback {
private val viewModel by viewModel<LibraryViewModel>()
private var adapter: LibraryAdapter? = null
private var selectionDecoration: MangaSelectionDecoration? = null
private var actionMode: ActionMode? = null
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): FragmentLibraryBinding {
return FragmentLibraryBinding.inflate(inflater, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val sizeResolver = ItemSizeResolver(resources, get())
val itemCLickListener = object : OnListItemClickListener<LibraryGroupModel> {
override fun onItemClick(item: LibraryGroupModel, view: View) {
}
}
selectionDecoration = MangaSelectionDecoration(view.context)
adapter = LibraryAdapter(
lifecycleOwner = viewLifecycleOwner,
coil = get(),
listener = this,
itemClickListener = itemCLickListener,
sizeResolver = sizeResolver,
selectionDecoration = checkNotNull(selectionDecoration),
)
binding.recyclerView.adapter = adapter
binding.recyclerView.setHasFixedSize(true)
viewModel.content.observe(viewLifecycleOwner, ::onListChanged)
viewModel.onError.observe(viewLifecycleOwner, ::onError)
}
override fun onDestroyView() {
super.onDestroyView()
adapter = null
selectionDecoration = null
actionMode = null
}
override fun onItemClick(item: Manga, view: View) {
if (selectionDecoration?.checkedItemsCount != 0) {
selectionDecoration?.toggleItemChecked(item.id)
if (selectionDecoration?.checkedItemsCount == 0) {
actionMode?.finish()
} else {
actionMode?.invalidate()
invalidateItemDecorations()
}
return
}
val intent = DetailsActivity.newIntent(view.context, item)
startActivity(intent)
}
override fun onItemLongClick(item: Manga, view: View): Boolean {
if (actionMode == null) {
actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this)
}
return actionMode?.also {
selectionDecoration?.setItemIsChecked(item.id, true)
invalidateItemDecorations()
it.invalidate()
} != null
}
override fun onRetryClick(error: Throwable) = Unit
override fun onTagRemoveClick(tag: MangaTag) = Unit
override fun onFilterClick() = Unit
override fun onEmptyActionClick() = Unit
override fun onWindowInsetsChanged(insets: Insets) {
binding.recyclerView.updatePadding(
left = insets.left,
right = insets.right,
top = insets.top,
bottom = insets.bottom,
)
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.menuInflater.inflate(R.menu.mode_remote, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
mode.title = selectionDecoration?.checkedItemsCount?.toString()
return true
}
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
val ctx = context ?: return false
return when (item.itemId) {
R.id.action_share -> {
ShareHelper(ctx).shareMangaLinks(collectSelectedItems())
mode.finish()
true
}
R.id.action_favourite -> {
FavouriteCategoriesBottomSheet.show(childFragmentManager, collectSelectedItems())
mode.finish()
true
}
R.id.action_save -> {
DownloadService.confirmAndStart(ctx, collectSelectedItems())
mode.finish()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode) {
selectionDecoration?.clearSelection()
invalidateItemDecorations()
actionMode = null
}
private fun collectSelectedItems(): Set<Manga> {
val ids = selectionDecoration?.checkedItemsIds
if (ids.isNullOrEmpty()) {
return emptySet()
}
return emptySet()//viewModel.getItems(ids)
}
private fun invalidateItemDecorations() {
binding.recyclerView.findViewsByType(RecyclerView::class.java).forEach {
it.invalidateItemDecorations()
}
}
private fun onListChanged(list: List<ListModel>) {
adapter?.items = list
}
private fun onError(e: Throwable) {
Snackbar.make(
binding.recyclerView,
e.getDisplayMessage(resources),
Snackbar.LENGTH_SHORT
).show()
}
companion object {
fun newInstance() = LibraryFragment()
}
}

View File

@@ -0,0 +1,78 @@
package org.koitharu.kotatsu.library.ui
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.list.domain.ListExtraProvider
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.ext.asLiveDataDistinct
class LibraryViewModel(
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val shortcutsRepository: ShortcutsRepository,
private val trackingRepository: TrackingRepository,
private val settings: AppSettings,
) : BaseViewModel(), ListExtraProvider {
val content: LiveData<List<ListModel>> = combine(
historyRepository.observeAllWithHistory(),
favouritesRepository.observeAllGrouped(SortOrder.NEWEST),
) { history, favourites ->
mapList(history, favourites)
}.catch { e ->
e.toErrorState(canRetry = false)
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
override suspend fun getCounter(mangaId: Long): Int {
return trackingRepository.getNewChaptersCount(mangaId)
}
override suspend fun getProgress(mangaId: Long): Float {
return if (settings.isReadingIndicatorsEnabled) {
historyRepository.getProgress(mangaId)
} else {
PROGRESS_NONE
}
}
private suspend fun mapList(
history: List<MangaWithHistory>,
favourites: Map<FavouriteCategory, List<Manga>>,
): List<ListModel> {
val result = ArrayList<ListModel>(favourites.keys.size + 1)
if (history.isNotEmpty()) {
result += LibraryGroupModel.History(mapHistory(history), null)
}
for ((category, list) in favourites) {
result += LibraryGroupModel.Favourites(list.toUi(ListMode.GRID, this), category)
}
return result
}
private suspend fun mapHistory(list: List<MangaWithHistory>): List<MangaItemModel> {
val showPercent = settings.isReadingIndicatorsEnabled
val result = ArrayList<MangaItemModel>(list.size)
for ((manga, history) in list) {
val counter = trackingRepository.getNewChaptersCount(manga.id)
val percent = if (showPercent) history.percent else PROGRESS_NONE
result += manga.toGridModel(counter, percent)
}
return result
}
}

View File

@@ -0,0 +1,60 @@
package org.koitharu.kotatsu.library.ui.adapter
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.*
import org.koitharu.kotatsu.list.ui.model.ListModel
import kotlin.jvm.internal.Intrinsics
class LibraryAdapter(
lifecycleOwner: LifecycleOwner,
coil: ImageLoader,
listener: MangaListListener,
sizeResolver: ItemSizeResolver,
selectionDecoration: MangaSelectionDecoration,
itemClickListener: OnListItemClickListener<LibraryGroupModel>,
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
init {
val pool = RecyclerView.RecycledViewPool()
delegatesManager
.addDelegate(
libraryGroupAD(
sharedPool = pool,
lifecycleOwner = lifecycleOwner,
coil = coil,
sizeResolver = sizeResolver,
selectionDecoration = selectionDecoration,
listener = listener,
itemClickListener = itemClickListener,
)
)
.addDelegate(loadingStateAD())
.addDelegate(loadingFooterAD())
.addDelegate(emptyStateListAD(listener))
.addDelegate(errorStateListAD(listener))
}
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
return when {
oldItem is LibraryGroupModel && newItem is LibraryGroupModel -> {
oldItem.key == newItem.key
}
else -> oldItem.javaClass == newItem.javaClass
}
}
override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
return Intrinsics.areEqual(oldItem, newItem)
}
}
}

View File

@@ -0,0 +1,48 @@
package org.koitharu.kotatsu.library.ui.adapter
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.databinding.ItemListGroupBinding
import org.koitharu.kotatsu.library.ui.model.LibraryGroupModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
fun libraryGroupAD(
sharedPool: RecyclerView.RecycledViewPool,
lifecycleOwner: LifecycleOwner,
coil: ImageLoader,
sizeResolver: ItemSizeResolver,
selectionDecoration: MangaSelectionDecoration,
listener: OnListItemClickListener<Manga>,
itemClickListener: OnListItemClickListener<LibraryGroupModel>,
) = adapterDelegateViewBinding<LibraryGroupModel, ListModel, ItemListGroupBinding>(
{ layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) }
) {
binding.recyclerView.setRecycledViewPool(sharedPool)
val adapter = ListDelegationAdapter(
mangaGridItemAD(coil, lifecycleOwner, listener, sizeResolver)
)
binding.recyclerView.addItemDecoration(selectionDecoration)
binding.recyclerView.adapter = adapter
val spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing)
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
val eventListener = AdapterDelegateClickListenerAdapter(this, itemClickListener)
itemView.setOnClickListener(eventListener)
bind {
binding.textViewTitle.text = item.getTitle(context.resources)
adapter.items = item.items
adapter.notifyDataSetChanged()
}
}

View File

@@ -0,0 +1,78 @@
package org.koitharu.kotatsu.library.ui.model
import android.content.res.Resources
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
sealed class LibraryGroupModel(
val items: List<MangaItemModel>
) : ListModel {
abstract val key: Any
abstract fun getTitle(resources: Resources): CharSequence
class History(
items: List<MangaItemModel>,
val timeAgo: DateTimeAgo?,
) : LibraryGroupModel(items) {
override val key: Any
get() = timeAgo?.javaClass ?: this::class.java
override fun getTitle(resources: Resources): CharSequence {
return timeAgo?.format(resources) ?: resources.getString(R.string.history)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as History
if (items != other.items) return false
if (timeAgo != other.timeAgo) return false
return true
}
override fun hashCode(): Int {
var result = items.hashCode()
result = 31 * result + (timeAgo?.hashCode() ?: 0)
return result
}
}
class Favourites(
items: List<MangaItemModel>,
val category: FavouriteCategory,
) : LibraryGroupModel(items) {
override val key: Any
get() = category.id
override fun getTitle(resources: Resources): CharSequence {
return category.title
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Favourites
if (items != other.items) return false
if (category != other.category) return false
return true
}
override fun hashCode(): Int {
var result = items.hashCode()
result = 31 * result + category.hashCode()
return result
}
}
}

View File

@@ -1,9 +1,9 @@
package org.koitharu.kotatsu.search.ui.multi.adapter package org.koitharu.kotatsu.list.ui
import android.content.res.Resources import android.content.res.Resources
import kotlin.math.roundToInt
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import kotlin.math.roundToInt
class ItemSizeResolver(resources: Resources, settings: AppSettings) { class ItemSizeResolver(resources: Resources, settings: AppSettings) {

View File

@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaGridModel import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.search.ui.multi.adapter.ItemSizeResolver import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.newImageRequest
import org.koitharu.kotatsu.utils.ext.referer import org.koitharu.kotatsu.utils.ext.referer

View File

@@ -1,19 +1,14 @@
package org.koitharu.kotatsu.main.ui package org.koitharu.kotatsu.main.ui
import android.app.ActivityOptions import android.app.ActivityOptions
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
import androidx.activity.result.ActivityResultCallback import androidx.activity.result.ActivityResultCallback
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.* import androidx.core.view.*
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit import androidx.fragment.app.commit
@@ -22,8 +17,6 @@ import androidx.transition.TransitionManager
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.LayoutParams.* import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.navigation.NavigationBarView
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -31,19 +24,15 @@ 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.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.prefs.AppSection
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.databinding.ActivityMainBinding import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.databinding.NavigationHeaderBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment
import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.history.ui.HistoryListFragment
import org.koitharu.kotatsu.local.ui.LocalListFragment import org.koitharu.kotatsu.library.ui.LibraryFragment
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
@@ -51,10 +40,8 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
import org.koitharu.kotatsu.settings.AppUpdateChecker import org.koitharu.kotatsu.settings.AppUpdateChecker
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
import org.koitharu.kotatsu.suggestions.ui.SuggestionsFragment
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
import org.koitharu.kotatsu.tracker.ui.FeedFragment import org.koitharu.kotatsu.tracker.ui.FeedFragment
import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.tracker.work.TrackWorker
@@ -74,12 +61,6 @@ class MainActivity :
private val viewModel by viewModel<MainViewModel>() private val viewModel by viewModel<MainViewModel>()
private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>() private val searchSuggestionViewModel by viewModel<SearchSuggestionViewModel>()
private lateinit var nav: NavigationBarView
private lateinit var navHeaderBinding: NavigationHeaderBinding
private var drawerToggle: ActionBarDrawerToggle? = null
private var drawer: DrawerLayout? = null
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback()) private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())
override val appBar: AppBarLayout override val appBar: AppBarLayout
@@ -88,28 +69,6 @@ class MainActivity :
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityMainBinding.inflate(layoutInflater)) setContentView(ActivityMainBinding.inflate(layoutInflater))
navHeaderBinding = NavigationHeaderBinding.inflate(layoutInflater)
nav = binding.bottomNav
drawer = binding.root as? DrawerLayout
drawerToggle = drawer?.let {
ActionBarDrawerToggle(
this,
it,
binding.toolbar,
R.string.open_menu,
R.string.close_menu
).apply {
setHomeAsUpIndicator(
ContextCompat.getDrawable(this@MainActivity, materialR.drawable.abc_ic_ab_back_material)
)
setToolbarNavigationClickListener {
binding.searchView.hideKeyboard()
onBackPressed()
}
it.addDrawerListener(this)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
}
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets -> ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) { if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
@@ -125,18 +84,15 @@ class MainActivity :
searchSuggestionListener = this@MainActivity searchSuggestionListener = this@MainActivity
} }
nav.setOnItemSelectedListener { item -> binding.bottomNav.setOnItemSelectedListener { item ->
when (item.itemId) { when (item.itemId) {
R.id.nav_local_storage -> { R.id.nav_library -> {
viewModel.defaultSection = AppSection.HISTORY setPrimaryFragment(LibraryFragment.newInstance())
setPrimaryFragment(HistoryListFragment.newInstance())
} }
R.id.nav_favourites -> { R.id.nav_explore -> {
viewModel.defaultSection = AppSection.FAVOURITES
setPrimaryFragment(FavouritesContainerFragment.newInstance()) setPrimaryFragment(FavouritesContainerFragment.newInstance())
} }
R.id.nav_feed -> { R.id.nav_feed -> {
viewModel.defaultSection = AppSection.FEED
setPrimaryFragment(FeedFragment.newInstance()) setPrimaryFragment(FeedFragment.newInstance())
} }
} }
@@ -160,22 +116,6 @@ class MainActivity :
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
} }
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
drawerToggle?.isDrawerIndicatorEnabled =
drawer?.getDrawerLockMode(GravityCompat.START) == DrawerLayout.LOCK_MODE_UNLOCKED
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
drawerToggle?.syncState()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
drawerToggle?.onConfigurationChanged(newConfig)
}
override fun onBackPressed() { override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH) val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
binding.searchView.clearFocus() binding.searchView.clearFocus()
@@ -189,12 +129,6 @@ class MainActivity :
} }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return drawerToggle?.onOptionsItemSelected(item) == true || when (item.itemId) {
else -> super.onOptionsItemSelected(item)
}
}
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.fab -> viewModel.openLastReader() R.id.fab -> viewModel.openLastReader()
@@ -278,12 +212,12 @@ class MainActivity :
override fun onSupportActionModeStarted(mode: ActionMode) { override fun onSupportActionModeStarted(mode: ActionMode) {
super.onSupportActionModeStarted(mode) super.onSupportActionModeStarted(mode)
adjustDrawerLock() showBottomNav(false)
} }
override fun onSupportActionModeFinished(mode: ActionMode) { override fun onSupportActionModeFinished(mode: ActionMode) {
super.onSupportActionModeFinished(mode) super.onSupportActionModeFinished(mode)
adjustDrawerLock() showBottomNav(true)
} }
private fun onOpenReader(manga: Manga) { private fun onOpenReader(manga: Manga) {
@@ -312,27 +246,23 @@ class MainActivity :
private fun onSearchOpened() { private fun onSearchOpened() {
TransitionManager.beginDelayedTransition(binding.appbar) TransitionManager.beginDelayedTransition(binding.appbar)
drawerToggle?.isDrawerIndicatorEnabled = false
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_NO_SCROLL scrollFlags = SCROLL_FLAG_NO_SCROLL
} }
binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant)) binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant))
binding.appbar.updatePadding(left = 0, right = 0) binding.appbar.updatePadding(left = 0, right = 0)
adjustDrawerLock()
adjustFabVisibility(isSearchOpened = true) adjustFabVisibility(isSearchOpened = true)
showBottomNav(false) showBottomNav(false)
} }
private fun onSearchClosed() { private fun onSearchClosed() {
TransitionManager.beginDelayedTransition(binding.appbar) TransitionManager.beginDelayedTransition(binding.appbar)
drawerToggle?.isDrawerIndicatorEnabled = true
binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> { binding.toolbarCard.updateLayoutParams<AppBarLayout.LayoutParams> {
scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS
} }
binding.appbar.background = null binding.appbar.background = null
val padding = resources.getDimensionPixelOffset(R.dimen.margin_normal) val padding = resources.getDimensionPixelOffset(R.dimen.margin_normal)
binding.appbar.updatePadding(left = padding, right = padding) binding.appbar.updatePadding(left = padding, right = padding)
adjustDrawerLock()
adjustFabVisibility(isSearchOpened = false) adjustFabVisibility(isSearchOpened = false)
showBottomNav(true) showBottomNav(true)
} }
@@ -369,7 +299,7 @@ class MainActivity :
isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true, isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true,
) { ) {
val fab = binding.fab val fab = binding.fab
if (isResumeEnabled && !isSearchOpened && topFragment is HistoryListFragment) { if (isResumeEnabled && !isSearchOpened && topFragment is LibraryFragment) {
if (!fab.isVisible) { if (!fab.isVisible) {
fab.show() fab.show()
} }
@@ -380,14 +310,6 @@ class MainActivity :
} }
} }
private fun adjustDrawerLock() {
val drawer = drawer ?: return
val isLocked = actionModeDelegate.isActionModeStarted || (drawerToggle?.isDrawerIndicatorEnabled == false)
drawer.setDrawerLockMode(
if (isLocked) DrawerLayout.LOCK_MODE_LOCKED_CLOSED else DrawerLayout.LOCK_MODE_UNLOCKED
)
}
private inner class VoiceInputCallback : ActivityResultCallback<String?> { private inner class VoiceInputCallback : ActivityResultCallback<String?> {
override fun onActivityResult(result: String?) { override fun onActivityResult(result: String?) {

View File

@@ -22,12 +22,12 @@ import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding
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.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.SearchActivity
import org.koitharu.kotatsu.search.ui.multi.adapter.ItemSizeResolver
import org.koitharu.kotatsu.search.ui.multi.adapter.MultiSearchAdapter import org.koitharu.kotatsu.search.ui.multi.adapter.MultiSearchAdapter
import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.findViewsByType import org.koitharu.kotatsu.utils.ext.findViewsByType

View File

@@ -6,6 +6,7 @@ import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
import coil.ImageLoader import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.list.ui.adapter.* import org.koitharu.kotatsu.list.ui.adapter.*
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel

View File

@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.databinding.ItemListGroupBinding import org.koitharu.kotatsu.databinding.ItemListGroupBinding
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
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

View File

@@ -86,7 +86,7 @@
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:clickable="true" android:clickable="true"
app:layout_insetEdge="bottom" app:layout_insetEdge="bottom"
app:menu="@menu/nav_menu" app:menu="@menu/nav_bottom"
tools:ignore="KeyboardInaccessibleWidget" /> tools:ignore="KeyboardInaccessibleWidget" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,7 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.recyclerview.widget.RecyclerView
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"
android:id="@+id/recyclerView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:clipToPadding="false"
</androidx.constraintlayout.widget.ConstraintLayout> android:orientation="vertical"
android:paddingLeft="@dimen/list_spacing"
android:paddingTop="@dimen/grid_spacing_outer"
android:paddingRight="@dimen/list_spacing"
android:paddingBottom="@dimen/grid_spacing_outer"
app:fastScrollEnabled="true"
app:layoutManager="org.koitharu.kotatsu.base.ui.list.FitHeightLinearLayoutManager" />

View File

@@ -3,12 +3,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android">
<item <item
android:id="@+id/nav_local_storage" android:id="@+id/nav_library"
android:icon="@drawable/ic_bookshelf_selector" android:icon="@drawable/ic_bookshelf_selector"
android:title="Library" /> android:title="Library" />
<item <item
android:id="@+id/nav_favourites" android:id="@+id/nav_explore"
android:icon="@drawable/ic_explore_selector" android:icon="@drawable/ic_explore_selector"
android:title="Explore" /> android:title="Explore" />