From fa02cfd7e835c6687e516ae220d5d5c3efaa1495 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 24 Nov 2020 06:58:16 +0200 Subject: [PATCH] Migrate details to AdapterDelegates and mvvm --- .../base/domain/MangaDataRepository.kt | 6 + .../kotatsu/base/domain/MangaIntent.kt | 33 +++++ .../koitharu/kotatsu/base/ui/BaseViewModel.kt | 14 +- .../base/ui/list/BaseRecyclerAdapter.kt | 1 + .../koitharu/kotatsu/details/DetailsModule.kt | 5 +- .../kotatsu/details/ui/ChaptersAdapter.kt | 93 ------------ .../kotatsu/details/ui/ChaptersFragment.kt | 99 ++++++------- .../kotatsu/details/ui/DetailsActivity.kt | 51 +++---- .../kotatsu/details/ui/DetailsFragment.kt | 45 +++--- .../kotatsu/details/ui/DetailsViewModel.kt | 140 +++++++++--------- .../ChapterListItemAD.kt} | 34 +++-- .../details/ui/adapter/ChaptersAdapter.kt | 40 +++++ .../ui/adapter/ChaptersSelectionDecoration.kt | 108 ++++++++++++++ .../details/ui/model/ChapterListItem.kt | 9 ++ .../ui/model/ListModelConversionExt.kt | 9 ++ .../kotatsu/favourites/data/FavouritesDao.kt | 4 + .../favourites/domain/FavouritesRepository.kt | 7 + .../kotatsu/history/data/HistoryDao.kt | 3 + .../kotatsu/history/domain/ChapterExtra.kt | 2 +- .../history/domain/HistoryRepository.kt | 7 + .../kotatsu/reader/ui/ChaptersDialog.kt | 23 +-- .../kotatsu/utils/SelectionController.kt | 10 ++ .../widget/recent/RecentListFactory.kt | 4 +- .../kotatsu/widget/shelf/ShelfListFactory.kt | 4 +- app/src/main/res/layout/item_chapter.xml | 14 -- 25 files changed, 427 insertions(+), 338 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt delete mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersAdapter.kt rename app/src/main/java/org/koitharu/kotatsu/details/ui/{ChapterHolder.kt => adapter/ChapterListItemAD.kt} (58%) create mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/SelectionController.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt index d8bf6facd..06f22090a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaDataRepository.kt @@ -32,6 +32,12 @@ class MangaDataRepository(private val db: MangaDatabase) { return db.mangaDao.find(mangaId)?.toManga() } + suspend fun resolveIntent(intent: MangaIntent): Manga? = when { + intent.manga != null -> intent.manga + intent.mangaId != MangaIntent.ID_NONE -> db.mangaDao.find(intent.mangaId)?.toManga() + else -> null // TODO resolve uri + } + suspend fun storeManga(manga: Manga) { val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) db.withTransaction { diff --git a/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt new file mode 100644 index 000000000..98b6522b5 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/domain/MangaIntent.kt @@ -0,0 +1,33 @@ +package org.koitharu.kotatsu.base.domain + +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import org.koitharu.kotatsu.core.model.Manga + +data class MangaIntent( + val manga: Manga?, + val mangaId: Long, + val uri: Uri? +) { + + companion object { + + fun from(intent: Intent?) = MangaIntent( + manga = intent?.getParcelableExtra(KEY_MANGA), + mangaId = intent?.getLongExtra(KEY_ID, ID_NONE) ?: ID_NONE, + uri = intent?.data + ) + + fun from(args: Bundle?) = MangaIntent( + manga = args?.getParcelable(KEY_MANGA), + mangaId = args?.getLong(KEY_ID, ID_NONE) ?: ID_NONE, + uri = null + ) + + const val ID_NONE = 0L + + const val KEY_MANGA = "manga" + const val KEY_ID = "id" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt index d1237f9ce..b3df5277a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseViewModel.kt @@ -6,6 +6,8 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.* import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.utils.SingleLiveEvent +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext abstract class BaseViewModel : ViewModel() { @@ -13,19 +15,21 @@ abstract class BaseViewModel : ViewModel() { val isLoading = MutableLiveData(false) protected fun launchJob( + context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit - ): Job = viewModelScope.launch(createErrorHandler(), start, block) + ): Job = viewModelScope.launch(context + createErrorHandler(), start, block) protected fun launchLoadingJob( + context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit - ): Job = viewModelScope.launch(createErrorHandler(), start) { - isLoading.value = true + ): Job = viewModelScope.launch(context + createErrorHandler(), start) { + isLoading.postValue(true) try { block() } finally { - isLoading.value = false + isLoading.postValue(false) } } @@ -34,7 +38,7 @@ abstract class BaseViewModel : ViewModel() { throwable.printStackTrace() } if (throwable !is CancellationException) { - onError.call(throwable) + onError.postCall(throwable) } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseRecyclerAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseRecyclerAdapter.kt index 0b169bc44..679cf2df2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseRecyclerAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/BaseRecyclerAdapter.kt @@ -5,6 +5,7 @@ import androidx.recyclerview.widget.RecyclerView import org.koin.core.component.KoinComponent import org.koitharu.kotatsu.utils.ext.replaceWith +@Deprecated("", replaceWith = ReplaceWith("AsyncListDifferDelegationAdapter")) abstract class BaseRecyclerAdapter(private val onItemClickListener: OnRecyclerItemClickListener? = null) : RecyclerView.Adapter>(), KoinComponent { diff --git a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt index c78b92d50..815358e2c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt @@ -2,10 +2,13 @@ package org.koitharu.kotatsu.details import org.koin.android.viewmodel.dsl.viewModel import org.koin.dsl.module +import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.details.ui.DetailsViewModel val detailsModule get() = module { - viewModel { DetailsViewModel(get(), get(), get(), get(), get()) } + viewModel { (intent: MangaIntent) -> + DetailsViewModel(intent, get(), get(), get(), get(), get()) + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersAdapter.kt deleted file mode 100644 index 71b5e9314..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersAdapter.kt +++ /dev/null @@ -1,93 +0,0 @@ -package org.koitharu.kotatsu.details.ui - -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.base.ui.list.BaseRecyclerAdapter -import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.history.domain.ChapterExtra - -class ChaptersAdapter(onItemClickListener: OnRecyclerItemClickListener) : - BaseRecyclerAdapter(onItemClickListener) { - - private val checkedIds = HashSet() - - val checkedItemsCount: Int - get() = checkedIds.size - - val checkedItemsIds: Set - get() = checkedIds - - var currentChapterId: Long? = null - set(value) { - field = value - updateCurrentPosition() - } - - var newChaptersCount: Int = 0 - set(value) { - val updated = maxOf(field, value) - field = value - notifyItemRangeChanged(itemCount - updated, updated) - } - - var currentChapterPosition = RecyclerView.NO_POSITION - private set - - fun clearChecked() { - checkedIds.clear() - notifyDataSetChanged() - } - - fun checkAll() { - for (item in dataSet) { - checkedIds.add(item.id) - } - notifyDataSetChanged() - } - - fun setItemIsChecked(itemId: Long, isChecked: Boolean) { - if ((isChecked && checkedIds.add(itemId)) || (!isChecked && checkedIds.remove(itemId))) { - val pos = findItemPositionById(itemId) - if (pos != RecyclerView.NO_POSITION) { - notifyItemChanged(pos) - } - } - } - - fun toggleItemChecked(itemId: Long) { - setItemIsChecked(itemId, itemId !in checkedIds) - } - - override fun onCreateViewHolder(parent: ViewGroup) = ChapterHolder(parent) - - override fun onGetItemId(item: MangaChapter) = item.id - - override fun getExtra(item: MangaChapter, position: Int): ChapterExtra = when { - item.id in checkedIds -> ChapterExtra.CHECKED - currentChapterPosition == RecyclerView.NO_POSITION - || currentChapterPosition < position -> if (position >= itemCount - newChaptersCount) { - ChapterExtra.NEW - } else { - ChapterExtra.UNREAD - } - currentChapterPosition == position -> ChapterExtra.CURRENT - currentChapterPosition > position -> ChapterExtra.READ - else -> ChapterExtra.UNREAD - } - - override fun onDataSetChanged() { - super.onDataSetChanged() - updateCurrentPosition() - } - - private fun updateCurrentPosition() { - val pos = currentChapterId?.let { - dataSet.indexOfFirst { x -> x.id == it } - } ?: RecyclerView.NO_POSITION - if (pos != currentChapterPosition) { - currentChapterPosition = pos - notifyDataSetChanged() - } - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt index b59fe89b8..789853318 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/ChaptersFragment.kt @@ -9,75 +9,66 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.view.isVisible import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.fragment_chapters.* import org.koin.android.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment -import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener -import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter +import org.koitharu.kotatsu.details.ui.adapter.ChaptersSelectionDecoration +import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.download.DownloadService import org.koitharu.kotatsu.reader.ui.ReaderActivity -import org.koitharu.kotatsu.utils.ext.resolveDp class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), - OnRecyclerItemClickListener, ActionMode.Callback { + OnListItemClickListener, ActionMode.Callback { private val viewModel by sharedViewModel() - private var manga: Manga? = null - - private lateinit var adapter: ChaptersAdapter + private var chaptersAdapter: ChaptersAdapter? = null private var actionMode: ActionMode? = null + private var selectionDecoration: ChaptersSelectionDecoration? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - adapter = ChaptersAdapter(this) - recyclerView_chapters.addItemDecoration( - DividerItemDecoration( - view.context, - RecyclerView.VERTICAL - ) - ) - recyclerView_chapters.setHasFixedSize(true) - recyclerView_chapters.adapter = adapter + chaptersAdapter = ChaptersAdapter(this) + selectionDecoration = ChaptersSelectionDecoration(view.context) + with(recyclerView_chapters) { + addItemDecoration(DividerItemDecoration(view.context, RecyclerView.VERTICAL)) + addItemDecoration(selectionDecoration!!) + setHasFixedSize(true) + adapter = chaptersAdapter + } - viewModel.mangaData.observe(viewLifecycleOwner, this::onMangaUpdated) viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) - viewModel.history.observe(viewLifecycleOwner, this::onHistoryChanged) - viewModel.newChapters.observe(viewLifecycleOwner, this::onNewChaptersChanged) + viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged) } - private fun onMangaUpdated(manga: Manga) { - this.manga = manga - adapter.replaceData(manga.chapters.orEmpty()) - scrollToCurrent() + override fun onDestroyView() { + chaptersAdapter = null + selectionDecoration = null + super.onDestroyView() + } + + private fun onChaptersChanged(list: List) { + chaptersAdapter?.items = list } private fun onLoadingStateChanged(isLoading: Boolean) { progressBar.isVisible = isLoading } - private fun onHistoryChanged(history: MangaHistory?) { - adapter.currentChapterId = history?.chapterId - scrollToCurrent() - } - - private fun onNewChaptersChanged(newChapters: Int) { - adapter.newChaptersCount = newChapters - } - - override fun onItemClick(item: MangaChapter, position: Int, view: View) { - if (adapter.checkedItemsCount != 0) { - adapter.toggleItemChecked(item.id) - if (adapter.checkedItemsCount == 0) { + override fun onItemClick(item: MangaChapter, view: View) { + if (selectionDecoration?.checkedItemsCount != 0) { + selectionDecoration?.toggleItemChecked(item.id) + if (selectionDecoration?.checkedItemsCount == 0) { actionMode?.finish() } else { actionMode?.invalidate() + recyclerView_chapters.invalidateItemDecorations() } return } @@ -91,44 +82,38 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), startActivity( ReaderActivity.newIntent( context ?: return, - manga ?: return, + viewModel.manga.value ?: return, item.id ), options.toBundle() ) } - override fun onItemLongClick(item: MangaChapter, position: Int, view: View): Boolean { + override fun onItemLongClick(item: MangaChapter, view: View): Boolean { if (actionMode == null) { actionMode = (activity as? AppCompatActivity)?.startSupportActionMode(this) } return actionMode?.also { - adapter.setItemIsChecked(item.id, true) + selectionDecoration?.setItemIsChecked(item.id, true) + recyclerView_chapters.invalidateItemDecorations() it.invalidate() } != null } - private fun scrollToCurrent() { - val pos = (recyclerView_chapters.adapter as? ChaptersAdapter)?.currentChapterPosition - ?: RecyclerView.NO_POSITION - if (pos != RecyclerView.NO_POSITION) { - (recyclerView_chapters.layoutManager as? LinearLayoutManager) - ?.scrollToPositionWithOffset(pos, resources.resolveDp(40)) - } - } - override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { return when (item.itemId) { R.id.action_save -> { DownloadService.start( context ?: return false, - manga ?: return false, - adapter.checkedItemsIds + viewModel.manga.value ?: return false, + selectionDecoration?.checkedItemsIds ) mode.finish() true } R.id.action_select_all -> { - adapter.checkAll() + val ids = chaptersAdapter?.items?.map { it.chapter.id } ?: return false + selectionDecoration?.checkAll(ids) + recyclerView_chapters.invalidateItemDecorations() mode.invalidate() true } @@ -137,6 +122,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), } override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + val manga = viewModel.manga.value mode.menuInflater.inflate(R.menu.mode_chapters, menu) menu.findItem(R.id.action_save).isVisible = manga?.source != MangaSource.LOCAL mode.title = manga?.title @@ -144,18 +130,19 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), } override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean { - val count = adapter.checkedItemsCount + val count = selectionDecoration?.checkedItemsCount ?: return false mode.subtitle = resources.getQuantityString( R.plurals.chapters_from_x, count, count, - adapter.itemCount + chaptersAdapter?.itemCount ?: 0 ) return true } override fun onDestroyActionMode(mode: ActionMode?) { - adapter.clearChecked() + selectionDecoration?.clearSelection() + recyclerView_chapters.invalidateItemDecorations() actionMode = null } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index fb06a3eaa..1ee1907f2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -7,20 +7,22 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.appcompat.view.ActionMode import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.net.toFile import androidx.lifecycle.lifecycleScope -import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import kotlinx.android.synthetic.main.activity_details.* import kotlinx.coroutines.launch import org.koin.android.viewmodel.ext.android.viewModel +import org.koin.core.parameter.parametersOf import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.core.model.Manga @@ -34,9 +36,9 @@ import org.koitharu.kotatsu.utils.ext.getThemeColor class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy { - private val viewModel by viewModel() - - private var manga: Manga? = null + private val viewModel by viewModel { + parametersOf(MangaIntent.from(intent)) + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -44,22 +46,14 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate supportActionBar?.setDisplayHomeAsUpEnabled(true) pager.adapter = MangaDetailsAdapter(this) TabLayoutMediator(tabs, pager, this).attach() - if (savedInstanceState == null) { - intent?.getParcelableExtra(EXTRA_MANGA)?.let { - viewModel.loadDetails(it, true) - } ?: intent?.getLongExtra(EXTRA_MANGA_ID, 0)?.takeUnless { it == 0L }?.let { - viewModel.findMangaById(it) - } ?: finishAfterTransition() - } - viewModel.mangaData.observe(this, ::onMangaUpdated) + viewModel.manga.observe(this, ::onMangaUpdated) + viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) viewModel.onMangaRemoved.observe(this, ::onMangaRemoved) viewModel.onError.observe(this, ::onError) - viewModel.newChapters.observe(this, ::onNewChaptersChanged) } private fun onMangaUpdated(manga: Manga) { - this.manga = manga title = manga.title invalidateOptionsMenu() } @@ -73,7 +67,7 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate } private fun onError(e: Throwable) { - if (manga == null) { + if (viewModel.manga.value == null) { Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show() finishAfterTransition() } else { @@ -98,8 +92,9 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate } override fun onPrepareOptionsMenu(menu: Menu): Boolean { + val manga = viewModel.manga.value menu.findItem(R.id.action_save).isVisible = - manga?.source != null && manga?.source != MangaSource.LOCAL + manga?.source != null && manga.source != MangaSource.LOCAL menu.findItem(R.id.action_delete).isVisible = manga?.source == MangaSource.LOCAL menu.findItem(R.id.action_browser).isVisible = @@ -111,7 +106,7 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) { R.id.action_share -> { - manga?.let { + viewModel.manga.value?.let { if (it.source == MangaSource.LOCAL) { ShareHelper.shareCbz(this, Uri.parse(it.url).toFile()) } else { @@ -121,8 +116,8 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate true } R.id.action_delete -> { - manga?.let { m -> - MaterialAlertDialogBuilder(this) + viewModel.manga.value?.let { m -> + AlertDialog.Builder(this) .setTitle(R.string.delete_manga) .setMessage(getString(R.string.text_delete_local_manga, m.title)) .setPositiveButton(R.string.delete) { _, _ -> @@ -134,10 +129,10 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate true } R.id.action_save -> { - manga?.let { + viewModel.manga.value?.let { val chaptersCount = it.chapters?.size ?: 0 if (chaptersCount > 5) { - MaterialAlertDialogBuilder(this) + AlertDialog.Builder(this) .setTitle(R.string.save_manga) .setMessage( getString( @@ -160,19 +155,19 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate true } R.id.action_browser -> { - manga?.let { + viewModel.manga.value?.let { startActivity(BrowserActivity.newIntent(this, it.url)) } true } R.id.action_related -> { - manga?.let { + viewModel.manga.value?.let { startActivity(GlobalSearchActivity.newIntent(this, it.title)) } true } R.id.action_shortcut -> { - manga?.let { + viewModel.manga.value?.let { lifecycleScope.launch { if (!MangaShortcut(it).requestPinShortcut(this@DetailsActivity)) { Snackbar.make( @@ -192,7 +187,6 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate tab.text = when (position) { 0 -> getString(R.string.details) 1 -> getString(R.string.chapters) - 2 -> getString(R.string.related) else -> null } } @@ -211,17 +205,14 @@ class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrate companion object { - private const val EXTRA_MANGA = "manga" - const val EXTRA_MANGA_ID = "manga_id" - const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA" fun newIntent(context: Context, manga: Manga) = Intent(context, DetailsActivity::class.java) - .putExtra(EXTRA_MANGA, manga) + .putExtra(MangaIntent.KEY_MANGA, manga) fun newIntent(context: Context, mangaId: Long) = Intent(context, DetailsActivity::class.java) - .putExtra(EXTRA_MANGA_ID, mangaId) + .putExtra(MangaIntent.KEY_ID, mangaId) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt index d5f5de283..c73bc1cf4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt @@ -29,23 +29,19 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis private val viewModel by sharedViewModel() - private var manga: Manga? = null - private var history: MangaHistory? = null - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.mangaData.observe(viewLifecycleOwner, ::onMangaUpdated) + viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated) viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged) - viewModel.history.observe(viewLifecycleOwner, ::onHistoryChanged) + viewModel.readingHistory.observe(viewLifecycleOwner, ::onHistoryChanged) } private fun onMangaUpdated(manga: Manga) { - this.manga = manga imageView_cover.newImageRequest(manga.largeCoverUrl ?: manga.coverUrl) .fallback(R.drawable.ic_placeholder) .crossfade(true) - .lifecycle(this) + .lifecycle(viewLifecycleOwner) .enqueueWith(coil) textView_title.text = manga.title textView_subtitle.textAndVisible = manga.altTitle @@ -94,12 +90,19 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis imageView_favourite.setOnClickListener(this) button_read.setOnClickListener(this) button_read.setOnLongClickListener(this) - updateReadButton() + button_read.isEnabled = !manga.chapters.isNullOrEmpty() } private fun onHistoryChanged(history: MangaHistory?) { - this.history = history - updateReadButton() + with(button_read) { + if (history == null) { + setText(R.string.read) + setIconResource(R.drawable.ic_read) + } else { + setText(R.string._continue) + setIconResource(R.drawable.ic_play) + } + } } private fun onFavouriteChanged(categories: List) { @@ -117,6 +120,7 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis } override fun onClick(v: View) { + val manga = viewModel.manga.value when { v.id == R.id.imageView_favourite -> { FavouriteCategoriesDialog.show(childFragmentManager, manga ?: return) @@ -126,7 +130,7 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis ReaderActivity.newIntent( context ?: return, manga ?: return, - history + viewModel.readingHistory.value ) ) } @@ -145,7 +149,7 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis override fun onLongClick(v: View): Boolean { when (v.id) { R.id.button_read -> { - if (history == null) { + if (viewModel.readingHistory.value == null) { return false } v.showPopupMenu(R.menu.popup_read) { @@ -154,7 +158,7 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis startActivity( ReaderActivity.newIntent( context ?: return@showPopupMenu false, - manga ?: return@showPopupMenu false + viewModel.manga.value ?: return@showPopupMenu false ) ) true @@ -167,19 +171,4 @@ class DetailsFragment : BaseFragment(R.layout.fragment_details), View.OnClickLis else -> return false } } - - private fun updateReadButton() { - if (manga?.chapters.isNullOrEmpty()) { - button_read.isEnabled = false - } else { - button_read.isEnabled = true - if (history == null) { - button_read.setText(R.string.read) - button_read.setIconResource(R.drawable.ic_read) - } else { - button_read.setText(R.string._continue) - button_read.setIconResource(R.drawable.ic_play) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 895feebd9..267f7e688 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -1,18 +1,18 @@ package org.koitharu.kotatsu.details.ui -import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.flow.* import org.koitharu.kotatsu.base.domain.MangaDataRepository +import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException -import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory +import org.koitharu.kotatsu.details.ui.model.toListItem import org.koitharu.kotatsu.favourites.domain.FavouritesRepository -import org.koitharu.kotatsu.favourites.domain.OnFavouritesChangeListener +import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.history.domain.HistoryRepository -import org.koitharu.kotatsu.history.domain.OnHistoryChangeListener import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.SingleLiveEvent @@ -20,88 +20,82 @@ import org.koitharu.kotatsu.utils.ext.safe import java.io.IOException class DetailsViewModel( + intent: MangaIntent, private val historyRepository: HistoryRepository, private val favouritesRepository: FavouritesRepository, private val localMangaRepository: LocalMangaRepository, private val trackingRepository: TrackingRepository, private val mangaDataRepository: MangaDataRepository -) : BaseViewModel(), OnHistoryChangeListener, OnFavouritesChangeListener { +) : BaseViewModel() { + + private val mangaData = MutableStateFlow(intent.manga) + + private val history = mangaData.mapNotNull { it?.id } + .distinctUntilChanged() + .flatMapLatest { mangaId -> + historyRepository.observeOne(mangaId) + }.stateIn(viewModelScope, SharingStarted.Eagerly, null) + + private val favourite = mangaData.mapNotNull { it?.id } + .distinctUntilChanged() + .flatMapLatest { mangaId -> + favouritesRepository.observeCategories(mangaId) + }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) + + private val newChapters = mangaData.mapNotNull { it?.id } + .distinctUntilChanged() + .mapLatest { mangaId -> + trackingRepository.getNewChaptersCount(mangaId) + }.stateIn(viewModelScope, SharingStarted.Eagerly, 0) + + val manga = mangaData.filterNotNull() + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val favouriteCategories = favourite + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val newChaptersCount = newChapters + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) + val readingHistory = history + .asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) - val mangaData = MutableLiveData() - val newChapters = MutableLiveData(0) val onMangaRemoved = SingleLiveEvent() - val history = MutableLiveData() - val favouriteCategories = MutableLiveData>() + + val chapters = combine( + mangaData.map { it?.chapters.orEmpty() }, + history.map { it?.chapterId }, + newChapters + ) { chapters, currentId, newCount -> + val currentIndex = chapters.indexOfFirst { it.id == currentId } + val firstNewIndex = chapters.size - newCount + chapters.mapIndexed { index, chapter -> + chapter.toListItem( + when { + index >= firstNewIndex -> ChapterExtra.NEW + index == currentIndex -> ChapterExtra.CURRENT + index < currentIndex -> ChapterExtra.READ + else -> ChapterExtra.UNREAD + } + ) + } + }.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default) init { - HistoryRepository.subscribe(this) - FavouritesRepository.subscribe(this) - } - - fun findMangaById(id: Long) { - launchLoadingJob { - val manga = mangaDataRepository.findMangaById(id) - ?: throw MangaNotFoundException("Cannot find manga by id") + launchLoadingJob(Dispatchers.Default) { + var manga = mangaDataRepository.resolveIntent(intent) + ?: throw MangaNotFoundException("Cannot find manga") + mangaData.value = manga + manga = manga.source.repository.getDetails(manga) mangaData.value = manga - loadDetails(manga, true) - } - } - - fun loadDetails(manga: Manga, force: Boolean = false) { - if (!force && mangaData.value == manga) { - return - } - loadHistory(manga) - mangaData.value = manga - loadFavourite(manga) - launchLoadingJob { - val data = withContext(Dispatchers.Default) { - manga.source.repository.getDetails(manga) - } - mangaData.value = data - newChapters.value = trackingRepository.getNewChaptersCount(manga.id) } } fun deleteLocal(manga: Manga) { - launchLoadingJob { - withContext(Dispatchers.Default) { - val original = localMangaRepository.getRemoteManga(manga) - localMangaRepository.delete(manga) || throw IOException("Unable to delete file") - safe { - historyRepository.deleteOrSwap(manga, original) - } + launchLoadingJob(Dispatchers.Default) { + val original = localMangaRepository.getRemoteManga(manga) + localMangaRepository.delete(manga) || throw IOException("Unable to delete file") + safe { + historyRepository.deleteOrSwap(manga, original) } - onMangaRemoved.call(manga) + onMangaRemoved.postCall(manga) } } - - private fun loadHistory(manga: Manga) { - launchJob { - history.value = historyRepository.getOne(manga) - } - } - - private fun loadFavourite(manga: Manga) { - launchJob { - favouriteCategories.value = favouritesRepository.getCategories(manga.id) - } - } - - override fun onHistoryChanged() { - loadHistory(mangaData.value ?: return) - } - - override fun onFavouritesChanged(mangaId: Long) { - val manga = mangaData.value ?: return - if (mangaId == manga.id) { - loadFavourite(manga) - } - } - - override fun onCleared() { - HistoryRepository.unsubscribe(this) - FavouritesRepository.unsubscribe(this) - super.onCleared() - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChapterHolder.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt similarity index 58% rename from app/src/main/java/org/koitharu/kotatsu/details/ui/ChapterHolder.kt rename to app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt index e28c00dc6..afd24ee42 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/ChapterHolder.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChapterListItemAD.kt @@ -1,23 +1,29 @@ -package org.koitharu.kotatsu.details.ui +package org.koitharu.kotatsu.details.ui.adapter -import android.graphics.Color -import android.view.ViewGroup -import androidx.core.view.isVisible +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateLayoutContainer import kotlinx.android.synthetic.main.item_chapter.* import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.base.ui.list.BaseViewHolder +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.history.domain.ChapterExtra import org.koitharu.kotatsu.utils.ext.getThemeColor -class ChapterHolder(parent: ViewGroup) : - BaseViewHolder(parent, R.layout.item_chapter) { +fun chapterListItemAD( + clickListener: OnListItemClickListener +) = adapterDelegateLayoutContainer(R.layout.item_chapter) { - override fun onBind(data: MangaChapter, extra: ChapterExtra) { - textView_title.text = data.name - textView_number.text = data.number.toString() - imageView_check.isVisible = extra == ChapterExtra.CHECKED - when (extra) { + itemView.setOnClickListener { + clickListener.onItemClick(item.chapter, it) + } + itemView.setOnLongClickListener { + clickListener.onItemLongClick(item.chapter, it) + } + + bind { payload -> + textView_title.text = item.chapter.name + textView_number.text = item.chapter.number.toString() + when (item.extra) { ChapterExtra.UNREAD -> { textView_number.setBackgroundResource(R.drawable.bg_badge_default) textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorSecondaryInverse)) @@ -34,10 +40,6 @@ class ChapterHolder(parent: ViewGroup) : textView_number.setBackgroundResource(R.drawable.bg_badge_accent) textView_number.setTextColor(context.getThemeColor(android.R.attr.textColorPrimaryInverse)) } - ChapterExtra.CHECKED -> { - textView_number.background = null - textView_number.setTextColor(Color.TRANSPARENT) - } } } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt new file mode 100644 index 000000000..f032dcc23 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersAdapter.kt @@ -0,0 +1,40 @@ +package org.koitharu.kotatsu.details.ui.adapter + +import androidx.recyclerview.widget.DiffUtil +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.details.ui.model.ChapterListItem +import kotlin.jvm.internal.Intrinsics + +class ChaptersAdapter( + onItemClickListener: OnListItemClickListener +) : AsyncListDifferDelegationAdapter(DiffCallback()) { + + init { + setHasStableIds(true) + delegatesManager.addDelegate(chapterListItemAD(onItemClickListener)) + } + + override fun getItemId(position: Int): Long { + return items[position].chapter.id + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: ChapterListItem, newItem: ChapterListItem): Boolean { + return oldItem.chapter.id == newItem.chapter.id + } + + override fun areContentsTheSame(oldItem: ChapterListItem, newItem: ChapterListItem): Boolean { + return Intrinsics.areEqual(oldItem, newItem) + } + + override fun getChangePayload(oldItem: ChapterListItem, newItem: ChapterListItem): Any? { + if (oldItem.extra != newItem.extra) { + return newItem.extra + } + return null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt new file mode 100644 index 000000000..f51ab3216 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/ChaptersSelectionDecoration.kt @@ -0,0 +1,108 @@ +package org.koitharu.kotatsu.details.ui.adapter + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import androidx.collection.ArraySet +import androidx.core.content.ContextCompat +import androidx.core.view.children +import androidx.recyclerview.widget.RecyclerView +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.utils.ext.getThemeColor +import org.koitharu.kotatsu.utils.ext.resolveDp + +class ChaptersSelectionDecoration(context: Context) : RecyclerView.ItemDecoration() { + + private val icon = ContextCompat.getDrawable(context, R.drawable.ic_check) + private val padding = context.resources.resolveDp(16) + private val bounds = Rect() + private val selection = ArraySet() + private val paint = Paint(Paint.ANTI_ALIAS_FLAG) + + init { + paint.color = context.getThemeColor(android.R.attr.colorControlActivated) + paint.style = Paint.Style.FILL + } + + val checkedItemsCount: Int + get() = selection.size + + val checkedItemsIds: Set + get() = selection + + fun toggleItemChecked(id: Long) { + if (!selection.remove(id)) { + selection.add(id) + } + } + + fun setItemIsChecked(id: Long, isChecked: Boolean) { + if (isChecked) { + selection.add(id) + } else { + selection.remove(id) + } + } + + fun checkAll(ids: Collection) { + selection.addAll(ids) + } + + fun clearSelection() { + selection.clear() + } + + override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + icon ?: return + canvas.save() + if (parent.clipToPadding) { + canvas.clipRect( + parent.paddingLeft, parent.paddingTop, parent.width - parent.paddingRight, + parent.height - parent.paddingBottom + ) + } + + for (child in parent.children) { + val itemId = parent.getChildItemId(child) + if (itemId in selection) { + parent.getDecoratedBoundsWithMargins(child, bounds) + bounds.offset(child.translationX.toInt(), child.translationY.toInt()) + canvas.drawRect(bounds, paint) + } + } + canvas.restore() + } + + override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { + icon ?: return + canvas.save() + val left: Int + val right: Int + if (parent.clipToPadding) { + left = parent.paddingLeft + right = parent.width - parent.paddingRight + canvas.clipRect( + left, parent.paddingTop, right, + parent.height - parent.paddingBottom + ) + } else { + left = 0 + right = parent.width + } + + for (child in parent.children) { + val itemId = parent.getChildItemId(child) + if (itemId in selection) { + parent.getDecoratedBoundsWithMargins(child, bounds) + bounds.offset(child.translationX.toInt(), child.translationY.toInt()) + val hh = (bounds.height() - icon.intrinsicHeight) / 2 + val top: Int = bounds.top + hh + val bottom: Int = bounds.bottom - hh + icon.setBounds(right - icon.intrinsicWidth - padding, top, right - padding, bottom) + icon.draw(canvas) + } + } + canvas.restore() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt new file mode 100644 index 000000000..5fad6e03a --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ChapterListItem.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.details.ui.model + +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.history.domain.ChapterExtra + +data class ChapterListItem( + val chapter: MangaChapter, + val extra: ChapterExtra +) diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt new file mode 100644 index 000000000..dc1df8e0f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/ListModelConversionExt.kt @@ -0,0 +1,9 @@ +package org.koitharu.kotatsu.details.ui.model + +import org.koitharu.kotatsu.core.model.MangaChapter +import org.koitharu.kotatsu.history.domain.ChapterExtra + +fun MangaChapter.toListItem(extra: ChapterExtra) = ChapterListItem( + chapter = this, + extra = extra +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt index c9d972417..fba33ef6b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouritesDao.kt @@ -38,6 +38,10 @@ abstract class FavouritesDao { @Query("SELECT * FROM favourites WHERE manga_id = :id GROUP BY manga_id") abstract suspend fun find(id: Long): FavouriteManga? + @Transaction + @Query("SELECT * FROM favourites WHERE manga_id = :id GROUP BY manga_id") + abstract fun observe(id: Long): Flow + @Insert(onConflict = OnConflictStrategy.IGNORE) abstract suspend fun insert(favourite: FavouriteEntity) 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 30a8d94d4..37a6f7e3b 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 @@ -4,6 +4,7 @@ import androidx.collection.ArraySet import androidx.room.withTransaction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.MangaEntity @@ -57,6 +58,12 @@ class FavouritesRepository(private val db: MangaDatabase) { return entities?.map { it.toFavouriteCategory() }.orEmpty() } + fun observeCategories(mangaId: Long): Flow> { + return db.favouritesDao.observe(mangaId).map { entity -> + entity?.categories?.map { it.toFavouriteCategory() }.orEmpty() + } + } + suspend fun addCategory(title: String): FavouriteCategory { val entity = FavouriteCategoryEntity( title = title, diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt index d121e1fe6..9ee2642fb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryDao.kt @@ -25,6 +25,9 @@ abstract class HistoryDao { @Query("SELECT * FROM history WHERE manga_id = :id") abstract suspend fun find(id: Long): HistoryEntity? + @Query("SELECT * FROM history WHERE manga_id = :id") + abstract fun observe(id: Long): Flow + @Query("DELETE FROM history") abstract suspend fun clear() diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/ChapterExtra.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/ChapterExtra.kt index 75d022623..32178c19d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/ChapterExtra.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/ChapterExtra.kt @@ -2,5 +2,5 @@ package org.koitharu.kotatsu.history.domain enum class ChapterExtra { - READ, CURRENT, UNREAD, NEW, CHECKED + READ, CURRENT, UNREAD, NEW } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt index 73ac1cd62..76f98590a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/domain/HistoryRepository.kt @@ -4,6 +4,7 @@ import androidx.collection.ArraySet import androidx.room.withTransaction import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -32,6 +33,12 @@ class HistoryRepository(private val db: MangaDatabase) : KoinComponent { } } + fun observeOne(id: Long): Flow { + return db.historyDao.observe(id).map { + it?.toMangaHistory() + } + } + suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Int) { val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) db.withTransaction { diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt index 7aca1935d..d2e8caf28 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ChaptersDialog.kt @@ -5,18 +5,17 @@ import android.view.View import androidx.appcompat.app.AlertDialog import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.dialog_chapters.* import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.AlertDialogFragment -import org.koitharu.kotatsu.base.ui.list.OnRecyclerItemClickListener +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.model.MangaChapter -import org.koitharu.kotatsu.details.ui.ChaptersAdapter +import org.koitharu.kotatsu.details.ui.adapter.ChaptersAdapter import org.koitharu.kotatsu.utils.ext.withArgs class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters), - OnRecyclerItemClickListener { + OnListItemClickListener { override fun onBuildDialog(builder: AlertDialog.Builder) { builder.setTitle(R.string.chapters) @@ -32,22 +31,12 @@ class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters), ) ) recyclerView_chapters.adapter = ChaptersAdapter(this).apply { - arguments?.getParcelableArrayList(ARG_CHAPTERS)?.let(this::replaceData) - currentChapterId = arguments?.getLong(ARG_CURRENT_ID, 0L)?.takeUnless { it == 0L } + // arguments?.getParcelableArrayList(ARG_CHAPTERS)?.let(this::setItems) + // currentChapterId = arguments?.getLong(ARG_CURRENT_ID, 0L)?.takeUnless { it == 0L } } } - override fun onResume() { - super.onResume() - val pos = (recyclerView_chapters.adapter as? ChaptersAdapter)?.currentChapterPosition - ?: RecyclerView.NO_POSITION - if (pos != RecyclerView.NO_POSITION) { - (recyclerView_chapters.layoutManager as? LinearLayoutManager) - ?.scrollToPositionWithOffset(pos, 100) - } - } - - override fun onItemClick(item: MangaChapter, position: Int, view: View) { + override fun onItemClick(item: MangaChapter, view: View) { ((parentFragment as? OnChapterChangeListener) ?: (activity as? OnChapterChangeListener))?.let { dismiss() diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/SelectionController.kt b/app/src/main/java/org/koitharu/kotatsu/utils/SelectionController.kt new file mode 100644 index 000000000..f3274af2e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/SelectionController.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.utils + +import kotlinx.coroutines.flow.MutableStateFlow + +class SelectionController { + + private val state = MutableStateFlow(emptySet()) + + +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt index 306cbdcfa..673bca9c0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt @@ -9,8 +9,8 @@ import coil.executeBlocking import coil.request.ImageRequest import kotlinx.coroutines.runBlocking import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.history.domain.HistoryRepository import org.koitharu.kotatsu.utils.ext.requireBitmap import java.io.IOException @@ -52,7 +52,7 @@ class RecentListFactory( views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder) } val intent = Intent() - intent.putExtra(DetailsActivity.EXTRA_MANGA_ID, item.id) + intent.putExtra(MangaIntent.KEY_ID, item.id) views.setOnClickFillInIntent(R.id.imageView_cover, intent) return views } diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt index f32ce51a3..dfe531baa 100644 --- a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt +++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt @@ -9,9 +9,9 @@ import coil.executeBlocking import coil.request.ImageRequest import kotlinx.coroutines.runBlocking import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.prefs.AppWidgetConfig -import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.utils.ext.requireBitmap import java.io.IOException @@ -63,7 +63,7 @@ class ShelfListFactory( views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder) } val intent = Intent() - intent.putExtra(DetailsActivity.EXTRA_MANGA_ID, item.id) + intent.putExtra(MangaIntent.KEY_ID, item.id) views.setOnClickFillInIntent(R.id.rootLayout, intent) return views } diff --git a/app/src/main/res/layout/item_chapter.xml b/app/src/main/res/layout/item_chapter.xml index e43df4b44..f43d53428 100644 --- a/app/src/main/res/layout/item_chapter.xml +++ b/app/src/main/res/layout/item_chapter.xml @@ -21,20 +21,6 @@ android:textColor="?android:textColorSecondaryInverse" tools:text="13" /> - -