diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index d7f25396e..88ea8c25d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -23,6 +23,7 @@ import org.koitharu.kotatsu.core.ui.uiModule import org.koitharu.kotatsu.details.detailsModule import org.koitharu.kotatsu.favourites.favouritesModule import org.koitharu.kotatsu.history.historyModule +import org.koitharu.kotatsu.library.libraryModule import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.local.localModule @@ -77,6 +78,7 @@ class KotatsuApp : Application() { suggestionsModule, shikimoriModule, bookmarksModule, + libraryModule, ) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt index 13a4c92c4..8d763ff29 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/domain/FavouritesRepository.kt @@ -7,6 +7,7 @@ import org.koitharu.kotatsu.core.db.entity.* import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity 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.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.SortOrder @@ -28,6 +29,11 @@ class FavouritesRepository( .mapItems { it.manga.toManga(it.tags.toMangaTags()) } } + fun observeAllGrouped(order: SortOrder): Flow>> { + return db.favouritesDao.observeAll(order) + .map { list -> groupByCategory(list) } + } + suspend fun getManga(categoryId: Long): List { val entities = db.favouritesDao.findAll(categoryId) return entities.map { it.manga.toManga(it.tags.toMangaTags()) } @@ -163,4 +169,16 @@ class FavouritesRepository( .map { x -> SortOrder(x.order, SortOrder.NEWEST) } .distinctUntilChanged() } + + private fun groupByCategory(list: List): Map> { + val map = HashMap>() + 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 + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/LibraryModule.kt b/app/src/main/java/org/koitharu/kotatsu/library/LibraryModule.kt new file mode 100644 index 000000000..940c71198 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/LibraryModule.kt @@ -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()) } + } \ No newline at end of file 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 new file mode 100644 index 000000000..ecffa7f33 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryFragment.kt @@ -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(), MangaListListener, ActionMode.Callback { + + private val viewModel by viewModel() + 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 { + 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 { + 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) { + adapter?.items = list + } + + private fun onError(e: Throwable) { + Snackbar.make( + binding.recyclerView, + e.getDisplayMessage(resources), + Snackbar.LENGTH_SHORT + ).show() + } + + companion object { + + fun newInstance() = LibraryFragment() + } +} \ 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 new file mode 100644 index 000000000..17b9d9ced --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/LibraryViewModel.kt @@ -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> = 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, + favourites: Map>, + ): List { + val result = ArrayList(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): List { + val showPercent = settings.isReadingIndicatorsEnabled + val result = ArrayList(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 + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt new file mode 100644 index 000000000..48d50fb0e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryAdapter.kt @@ -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, +) : AsyncListDifferDelegationAdapter(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() { + + 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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryGroupAD.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryGroupAD.kt new file mode 100644 index 000000000..309b056d1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/adapter/LibraryGroupAD.kt @@ -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, + itemClickListener: OnListItemClickListener, +) = adapterDelegateViewBinding( + { 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() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibraryGroupModel.kt b/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibraryGroupModel.kt new file mode 100644 index 000000000..c1cccbf02 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/library/ui/model/LibraryGroupModel.kt @@ -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 +) : ListModel { + + abstract val key: Any + abstract fun getTitle(resources: Resources): CharSequence + + class History( + items: List, + 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, + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/ItemSizeResolver.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/ItemSizeResolver.kt similarity index 88% rename from app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/ItemSizeResolver.kt rename to app/src/main/java/org/koitharu/kotatsu/list/ui/ItemSizeResolver.kt index a5f5d3f72..bd406a658 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/ItemSizeResolver.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/ItemSizeResolver.kt @@ -1,9 +1,9 @@ -package org.koitharu.kotatsu.search.ui.multi.adapter +package org.koitharu.kotatsu.list.ui import android.content.res.Resources -import kotlin.math.roundToInt import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.prefs.AppSettings +import kotlin.math.roundToInt class ItemSizeResolver(resources: Resources, settings: AppSettings) { diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt index 8145e240e..083294e58 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt @@ -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.MangaGridModel 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.newImageRequest import org.koitharu.kotatsu.utils.ext.referer 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 c90e5e915..aa0ca32aa 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 @@ -1,19 +1,14 @@ package org.koitharu.kotatsu.main.ui import android.app.ActivityOptions -import android.content.res.Configuration import android.os.Bundle -import android.view.MenuItem import android.view.View import android.view.ViewGroup.MarginLayoutParams import androidx.activity.result.ActivityResultCallback -import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.view.ActionMode import androidx.core.app.ActivityOptionsCompat -import androidx.core.content.ContextCompat import androidx.core.graphics.Insets import androidx.core.view.* -import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction 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.LayoutParams.* 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 kotlinx.coroutines.Dispatchers 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.koitharu.kotatsu.R 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.databinding.ActivityMainBinding -import org.koitharu.kotatsu.databinding.NavigationHeaderBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.favourites.ui.FavouritesContainerFragment 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.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag 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.SearchActivity 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.SearchSuggestionViewModel import org.koitharu.kotatsu.settings.AppUpdateChecker -import org.koitharu.kotatsu.settings.SettingsActivity import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment 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.tracker.ui.FeedFragment import org.koitharu.kotatsu.tracker.work.TrackWorker @@ -74,12 +61,6 @@ class MainActivity : private val viewModel by viewModel() private val searchSuggestionViewModel by viewModel() - - 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()) override val appBar: AppBarLayout @@ -88,28 +69,6 @@ class MainActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) 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 -> if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) { @@ -125,18 +84,15 @@ class MainActivity : searchSuggestionListener = this@MainActivity } - nav.setOnItemSelectedListener { item -> + binding.bottomNav.setOnItemSelectedListener { item -> when (item.itemId) { - R.id.nav_local_storage -> { - viewModel.defaultSection = AppSection.HISTORY - setPrimaryFragment(HistoryListFragment.newInstance()) + R.id.nav_library -> { + setPrimaryFragment(LibraryFragment.newInstance()) } - R.id.nav_favourites -> { - viewModel.defaultSection = AppSection.FAVOURITES + R.id.nav_explore -> { setPrimaryFragment(FavouritesContainerFragment.newInstance()) } R.id.nav_feed -> { - viewModel.defaultSection = AppSection.FEED setPrimaryFragment(FeedFragment.newInstance()) } } @@ -160,22 +116,6 @@ class MainActivity : 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() { val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH) 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) { when (v.id) { R.id.fab -> viewModel.openLastReader() @@ -278,12 +212,12 @@ class MainActivity : override fun onSupportActionModeStarted(mode: ActionMode) { super.onSupportActionModeStarted(mode) - adjustDrawerLock() + showBottomNav(false) } override fun onSupportActionModeFinished(mode: ActionMode) { super.onSupportActionModeFinished(mode) - adjustDrawerLock() + showBottomNav(true) } private fun onOpenReader(manga: Manga) { @@ -312,27 +246,23 @@ class MainActivity : private fun onSearchOpened() { TransitionManager.beginDelayedTransition(binding.appbar) - drawerToggle?.isDrawerIndicatorEnabled = false binding.toolbarCard.updateLayoutParams { scrollFlags = SCROLL_FLAG_NO_SCROLL } binding.appbar.setBackgroundColor(getThemeColor(materialR.attr.colorSurfaceVariant)) binding.appbar.updatePadding(left = 0, right = 0) - adjustDrawerLock() adjustFabVisibility(isSearchOpened = true) showBottomNav(false) } private fun onSearchClosed() { TransitionManager.beginDelayedTransition(binding.appbar) - drawerToggle?.isDrawerIndicatorEnabled = true binding.toolbarCard.updateLayoutParams { scrollFlags = SCROLL_FLAG_SCROLL or SCROLL_FLAG_ENTER_ALWAYS } binding.appbar.background = null val padding = resources.getDimensionPixelOffset(R.dimen.margin_normal) binding.appbar.updatePadding(left = padding, right = padding) - adjustDrawerLock() adjustFabVisibility(isSearchOpened = false) showBottomNav(true) } @@ -369,7 +299,7 @@ class MainActivity : isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true, ) { val fab = binding.fab - if (isResumeEnabled && !isSearchOpened && topFragment is HistoryListFragment) { + if (isResumeEnabled && !isSearchOpened && topFragment is LibraryFragment) { if (!fab.isVisible) { 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 { override fun onActivityResult(result: String?) { diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt index 5bd119a83..3d7dca577 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/MultiSearchActivity.kt @@ -22,12 +22,12 @@ import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding 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.list.ui.ItemSizeResolver import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag 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.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.findViewsByType diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/MultiSearchAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/MultiSearchAdapter.kt index 35afb49d4..1ea3d2988 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/MultiSearchAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/MultiSearchAdapter.kt @@ -6,6 +6,7 @@ import androidx.recyclerview.widget.RecyclerView.RecycledViewPool import coil.ImageLoader import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter 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.adapter.* import org.koitharu.kotatsu.list.ui.model.ListModel diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt index ee58933e8..52d819430 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt @@ -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.decor.SpacingItemDecoration 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.adapter.mangaGridItemAD import org.koitharu.kotatsu.list.ui.model.ListModel diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 568475b4b..bfe002239 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -86,7 +86,7 @@ android:layout_gravity="bottom" android:clickable="true" app:layout_insetEdge="bottom" - app:menu="@menu/nav_menu" + app:menu="@menu/nav_bottom" tools:ignore="KeyboardInaccessibleWidget" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_library.xml b/app/src/main/res/layout/fragment_library.xml index 44cb66b71..157bad984 100644 --- a/app/src/main/res/layout/fragment_library.xml +++ b/app/src/main/res/layout/fragment_library.xml @@ -1,7 +1,15 @@ - - - \ No newline at end of file + android:layout_height="match_parent" + android:clipToPadding="false" + 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" /> \ No newline at end of file diff --git a/app/src/main/res/menu/nav_menu.xml b/app/src/main/res/menu/nav_bottom.xml similarity index 85% rename from app/src/main/res/menu/nav_menu.xml rename to app/src/main/res/menu/nav_bottom.xml index 5367696e6..b73e6cbad 100644 --- a/app/src/main/res/menu/nav_menu.xml +++ b/app/src/main/res/menu/nav_bottom.xml @@ -3,12 +3,12 @@ xmlns:android="http://schemas.android.com/apk/res/android">