From 445ff893921897d4b727742ce0dfcb7afe1006f7 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 11 Mar 2022 19:24:10 +0200 Subject: [PATCH 01/84] Enhance nsfw detection and indication --- .../core/parser/site/GroupleRepository.kt | 2 + .../core/parser/site/HentaiLibRepository.kt | 2 + .../core/parser/site/MangaLibRepository.kt | 7 + .../kotatsu/details/ui/DetailsFragment.kt | 79 ++-- .../layout-w600dp-land/fragment_details.xml | 413 +++++++----------- .../layout-w600dp-port/fragment_details.xml | 353 ++++++--------- app/src/main/res/layout/fragment_details.xml | 351 ++++++--------- .../main/res/layout/layout_details_info.xml | 95 ++++ app/src/main/res/values/strings.xml | 1 + 9 files changed, 562 insertions(+), 741 deletions(-) create mode 100644 app/src/main/res/layout/layout_details_info.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt index 0aeb751e1..b353aae80 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/GroupleRepository.kt @@ -13,6 +13,7 @@ import java.util.* private const val PAGE_SIZE = 70 private const val PAGE_SIZE_SEARCH = 50 +private const val NSFW_ALERT = "сексуальные сцены" abstract class GroupleRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepository(loaderContext) { @@ -131,6 +132,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) : source = source ) }, + isNsfw = root.select(".alert-warning").any { it.ownText().contains(NSFW_ALERT) }, chapters = root.selectFirst("div.chapters-link")?.selectFirst("table") ?.select("tr:has(td > a)")?.asReversed()?.mapIndexedNotNull { i, tr -> val a = tr.selectFirst("a") ?: return@mapIndexedNotNull null diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HentaiLibRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HentaiLibRepository.kt index 2a089cb6a..c15eca844 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HentaiLibRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/HentaiLibRepository.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.core.parser.site +import org.jsoup.nodes.Document import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.model.MangaSource @@ -9,4 +10,5 @@ class HentaiLibRepository(loaderContext: MangaLoaderContext) : MangaLibRepositor override val source = MangaSource.HENTAILIB + override fun isNsfw(doc: Document) = true } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt index 87868b327..d25c7a959 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/MangaLibRepository.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.core.parser.site import androidx.collection.ArraySet import org.json.JSONArray import org.json.JSONObject +import org.jsoup.nodes.Document import org.koitharu.kotatsu.base.domain.MangaLoaderContext import org.koitharu.kotatsu.core.exceptions.AuthRequiredException import org.koitharu.kotatsu.core.exceptions.ParseException @@ -148,6 +149,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) : source = source ) } ?: manga.tags, + isNsfw = isNsfw(doc), description = info?.selectFirst("div.media-description__text")?.html(), chapters = chapters ) @@ -230,6 +232,11 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) : return body.selectFirst(".profile-user__username")?.text() ?: parseFailed("Cannot find username") } + protected open fun isNsfw(doc: Document): Boolean { + val sidebar = doc.body().selectFirst(".media-sidebar") ?: parseFailed("Sidebar not found") + return sidebar.getElementsContainingOwnText("18+").isNotEmpty() + } + private fun getSortKey(sortOrder: SortOrder?) = when (sortOrder) { SortOrder.RATING -> "desc&sort=rate" SortOrder.ALPHABETICAL -> "asc&sort=name" 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 98425c798..3cd328ce2 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 @@ -7,7 +7,7 @@ import android.text.method.LinkMovementMethod import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.core.content.res.ResourcesCompat +import androidx.core.content.ContextCompat import androidx.core.graphics.Insets import androidx.core.net.toUri import androidx.core.text.parseAsHtml @@ -52,7 +52,7 @@ class DetailsFragment : BaseFragment(), View.OnClickList binding.buttonFavorite.setOnClickListener(this) binding.buttonRead.setOnClickListener(this) binding.buttonRead.setOnLongClickListener(this) - binding.coverCard.setOnClickListener(this) + binding.imageViewCover.setOnClickListener(this) binding.textViewDescription.movementMethod = LinkMovementMethod.getInstance() viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated) viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) @@ -67,61 +67,61 @@ class DetailsFragment : BaseFragment(), View.OnClickList textViewTitle.text = manga.title textViewSubtitle.textAndVisible = manga.altTitle textViewAuthor.textAndVisible = manga.author - sourceContainer.isVisible = manga.source != MangaSource.LOCAL - textViewSource.text = manga.source.title - textViewDescription.text = - manga.description?.parseAsHtml()?.takeUnless(Spanned::isBlank) - ?: getString(R.string.no_description) + textViewDescription.text = manga.description?.parseAsHtml()?.takeUnless(Spanned::isBlank) + ?: getString(R.string.no_description) when (manga.state) { MangaState.FINISHED -> { textViewState.apply { textAndVisible = resources.getString(R.string.state_finished) - drawableStart = ResourcesCompat.getDrawable(resources, - R.drawable.ic_state_finished, - context.theme) + drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_state_finished) } } MangaState.ONGOING -> { textViewState.apply { textAndVisible = resources.getString(R.string.state_ongoing) - drawableStart = ResourcesCompat.getDrawable(resources, - R.drawable.ic_state_ongoing, - context.theme) + drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_state_ongoing) } } else -> textViewState.isVisible = false } // Info containers - if (manga.chapters?.isNotEmpty() == true) { - chaptersContainer.isVisible = true - textViewChapters.text = manga.chapters.let { - resources.getQuantityString( - R.plurals.chapters, - it.size, - manga.chapters.size - ) - } + if (manga.chapters.isNullOrEmpty()) { + infoLayout.textViewChapters.isVisible = false } else { - chaptersContainer.isVisible = false + infoLayout.textViewChapters.isVisible = true + infoLayout.textViewChapters.text = resources.getQuantityString( + R.plurals.chapters, + manga.chapters.size, + manga.chapters.size, + ) } if (manga.rating == Manga.NO_RATING) { - ratingContainer.isVisible = false + infoLayout.ratingContainer.isVisible = false } else { - textViewRating.text = String.format("%.1f", manga.rating * 5) - ratingContainer.isVisible = true + infoLayout.textViewRating.text = String.format("%.1f", manga.rating * 5) + infoLayout.ratingContainer.isVisible = true } - val file = manga.url.toUri().toFileOrNull() - if (file != null) { - viewLifecycleScope.launch { - val size = file.computeSize() - textViewSize.text = FileSize.BYTES.format(requireContext(), size) + if (manga.source == MangaSource.LOCAL) { + infoLayout.textViewSource.isVisible = false + val file = manga.url.toUri().toFileOrNull() + if (file != null) { + viewLifecycleScope.launch { + val size = file.computeSize() + infoLayout.textViewSize.text = FileSize.BYTES.format(requireContext(), size) + infoLayout.textViewSize.isVisible = true + } + } else { + infoLayout.textViewSize.isVisible = false } - sizeContainer.isVisible = true } else { - sizeContainer.isVisible = false + infoLayout.textViewSource.text = manga.source.title + infoLayout.textViewSource.isVisible = true + infoLayout.textViewSize.isVisible = false } + infoLayout.textViewNsfw.isVisible = manga.isNsfw + // Buttons buttonRead.isEnabled = !manga.chapters.isNullOrEmpty() @@ -143,13 +143,12 @@ class DetailsFragment : BaseFragment(), View.OnClickList } private fun onFavouriteChanged(isFavourite: Boolean) { - with(binding.buttonFavorite) { - if (isFavourite) { - this.setIconResource(R.drawable.ic_heart) - } else { - this.setIconResource(R.drawable.ic_heart_outline) - } + val iconRes = if (isFavourite) { + R.drawable.ic_heart + } else { + R.drawable.ic_heart_outline } + binding.buttonFavorite.setIconResource(iconRes) } private fun onLoadingStateChanged(isLoading: Boolean) { @@ -189,7 +188,7 @@ class DetailsFragment : BaseFragment(), View.OnClickList ) ) } - R.id.cover_card -> { + R.id.imageView_cover -> { val options = ActivityOptions.makeSceneTransitionAnimation( requireActivity(), binding.imageViewCover, diff --git a/app/src/main/res/layout-w600dp-land/fragment_details.xml b/app/src/main/res/layout-w600dp-land/fragment_details.xml index 4be412a26..55df1bb95 100644 --- a/app/src/main/res/layout-w600dp-land/fragment_details.xml +++ b/app/src/main/res/layout-w600dp-land/fragment_details.xml @@ -13,262 +13,171 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + + + + + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/barrier_header" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintWidth_percent="0.3" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover" + tools:background="@tools:sample/backgrounds/scenic" + tools:ignore="ContentDescription,UnusedAttribute" /> - + - + - + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + app:layout_constraintTop_toBottomOf="@+id/barrier_header" /> + tools:text="@tools:sample/lorem/random[250]" /> - - - - - - - - - - - - - - - - - - - - - - - + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:foreground="?selectableItemBackground" + android:scaleType="centerCrop" + android:transitionName="cover" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintWidth_percent="0.3" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover" + tools:background="@tools:sample/backgrounds/scenic" + tools:ignore="ContentDescription,UnusedAttribute" /> - + - + - + - + - + - - - - - - - - - - - - - - - - - + app:layout_constraintTop_toBottomOf="@id/barrier_header" /> - + - - - + + app:layout_constraintTop_toBottomOf="@+id/button_read" /> + tools:text="@tools:sample/lorem/random[250]" /> + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a5dc3b73a..238e096f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -266,4 +266,5 @@ Always Preload pages Logged in as %s + 18+ \ No newline at end of file From 0b07e83e3cbf56c9da5c899bf0be18ec4f3cd360 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 11 Mar 2022 19:22:31 +0200 Subject: [PATCH 02/84] Enhance tablet ui --- .../kotatsu/details/ui/ChaptersFragment.kt | 21 +++- .../kotatsu/details/ui/DetailsActivity.kt | 34 +++---- .../kotatsu/details/ui/DetailsFragment.kt | 20 +++- .../koitharu/kotatsu/main/ui/MainActivity.kt | 72 +++++++------- .../koitharu/kotatsu/utils/ext/InsetsExt.kt | 20 ++++ .../activity_details.xml | 0 .../layout-w720dp-land/activity_details.xml | 55 +++++++++++ .../res/layout-w720dp-land/activity_main.xml | 96 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 2 +- app/src/main/res/values-w980dp/dimens.xml | 6 ++ app/src/main/res/values/dimens.xml | 4 + 11 files changed, 267 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt rename app/src/main/res/{layout-w600dp => layout-land}/activity_details.xml (100%) create mode 100644 app/src/main/res/layout-w720dp-land/activity_details.xml create mode 100644 app/src/main/res/layout-w720dp-land/activity_main.xml create mode 100644 app/src/main/res/values-w980dp/dimens.xml 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 6ea44a8ac..4f4d1e1a5 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,6 +9,8 @@ import androidx.appcompat.view.ActionMode import androidx.core.graphics.Insets import androidx.core.view.isVisible import androidx.core.view.updatePadding +import androidx.core.view.updatePaddingRelative +import androidx.fragment.app.FragmentContainerView import androidx.recyclerview.widget.RecyclerView import com.google.android.material.divider.MaterialDividerItemDecoration import org.koin.androidx.viewmodel.ext.android.sharedViewModel @@ -24,6 +26,7 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.download.ui.service.DownloadService import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.utils.ext.getEnd class ChaptersFragment : BaseFragment(), OnListItemClickListener, @@ -200,11 +203,19 @@ class ChaptersFragment : BaseFragment(), } override fun onWindowInsetsChanged(insets: Insets) { - binding.recyclerViewChapters.updatePadding( - left = insets.left, - right = insets.right, - bottom = insets.bottom + binding.spinnerBranches.height - ) + val root = binding.root + if (root.parent is FragmentContainerView) { + binding.recyclerViewChapters.updatePaddingRelative( + end = insets.getEnd(root), + bottom = insets.bottom + binding.spinnerBranches.height, + ) + } else { + binding.recyclerViewChapters.updatePadding( + left = insets.left, + right = insets.right, + bottom = insets.bottom + binding.spinnerBranches.height, + ) + } } private fun onChaptersChanged(list: List) { 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 d8cb87ecf..7bea427f3 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 @@ -17,14 +17,12 @@ import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding 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.coroutines.launch import org.koin.android.ext.android.get import org.koin.androidx.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 @@ -43,8 +41,7 @@ import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.buildAlertDialog import org.koitharu.kotatsu.utils.ext.getDisplayMessage -class DetailsActivity : BaseActivity(), - TabLayoutMediator.TabConfigurationStrategy { +class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy { private val viewModel by viewModel { parametersOf(MangaIntent(intent)) @@ -54,8 +51,11 @@ class DetailsActivity : BaseActivity(), super.onCreate(savedInstanceState) setContentView(ActivityDetailsBinding.inflate(layoutInflater)) supportActionBar?.setDisplayHomeAsUpEnabled(true) - binding.pager.adapter = MangaDetailsAdapter(this) - TabLayoutMediator(binding.tabs, binding.pager, this).attach() + val pager = binding.pager + if (pager != null) { + pager.adapter = MangaDetailsAdapter(this) + TabLayoutMediator(checkNotNull(binding.tabs), pager, this).attach() + } viewModel.manga.observe(this, ::onMangaUpdated) viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) @@ -105,8 +105,9 @@ class DetailsActivity : BaseActivity(), topMargin = insets.top } } - if (binding.tabs.parent !is Toolbar) { - binding.tabs.updatePadding( + val tabs = binding.tabs + if (tabs != null && tabs.parent !is Toolbar) { + tabs.updatePadding( left = insets.left, right = insets.right ) @@ -114,7 +115,7 @@ class DetailsActivity : BaseActivity(), } private fun onNewChaptersChanged(newChapters: Int) { - val tab = binding.tabs.getTabAt(1) ?: return + val tab = binding.tabs?.getTabAt(1) ?: return if (newChapters == 0) { tab.removeBadge() } else { @@ -208,11 +209,7 @@ class DetailsActivity : BaseActivity(), viewModel.manga.value?.let { lifecycleScope.launch { if (!get().requestPinShortcut(it)) { - Snackbar.make( - binding.pager, - R.string.operation_not_supported, - Snackbar.LENGTH_SHORT - ).show() + binding.snackbar.show(getString(R.string.operation_not_supported)) } } } @@ -231,19 +228,18 @@ class DetailsActivity : BaseActivity(), override fun onSupportActionModeStarted(mode: ActionMode) { super.onSupportActionModeStarted(mode) - binding.pager.isUserInputEnabled = false + binding.pager?.isUserInputEnabled = false } override fun onSupportActionModeFinished(mode: ActionMode) { super.onSupportActionModeFinished(mode) - binding.pager.isUserInputEnabled = true + binding.pager?.isUserInputEnabled = true } fun showChapterMissingDialog(chapterId: Long) { val remoteManga = viewModel.getRemoteManga() if (remoteManga == null) { - Snackbar.make(binding.pager, R.string.chapter_is_missing, Snackbar.LENGTH_LONG) - .show() + binding.snackbar.show(getString( R.string.chapter_is_missing)) return } buildAlertDialog(this) { @@ -268,8 +264,6 @@ class DetailsActivity : BaseActivity(), companion object { - const val ACTION_MANGA_VIEW = "${BuildConfig.APPLICATION_ID}.action.VIEW_MANGA" - fun newIntent(context: Context, manga: Manga): Intent { return Intent(context, DetailsActivity::class.java) .putExtra(MangaIntent.KEY_MANGA, manga) 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 3cd328ce2..47993dd60 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 @@ -13,6 +13,8 @@ import androidx.core.net.toUri import androidx.core.text.parseAsHtml import androidx.core.view.isVisible import androidx.core.view.updatePadding +import androidx.core.view.updatePaddingRelative +import androidx.fragment.app.FragmentContainerView import coil.ImageLoader import coil.request.ImageRequest import coil.util.CoilUtils @@ -232,11 +234,19 @@ class DetailsFragment : BaseFragment(), View.OnClickList } override fun onWindowInsetsChanged(insets: Insets) { - binding.root.updatePadding( - left = insets.left, - right = insets.right, - bottom = insets.bottom - ) + val root = binding.root + if (root.parent is FragmentContainerView) { + root.updatePaddingRelative( + start = insets.getStart(root), + bottom = insets.bottom, + ) + } else { + root.updatePadding( + left = insets.left, + right = insets.right, + bottom = insets.bottom, + ) + } } private fun bindTags(manga: Manga) { 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 3a3cea458..474ea72ba 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 @@ -53,10 +53,7 @@ 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 -import org.koitharu.kotatsu.utils.ext.getDisplayMessage -import org.koitharu.kotatsu.utils.ext.hideKeyboard -import org.koitharu.kotatsu.utils.ext.measureHeight -import org.koitharu.kotatsu.utils.ext.resolveDp +import org.koitharu.kotatsu.utils.ext.* private const val TAG_PRIMARY = "primary" private const val TAG_SEARCH = "search" @@ -69,7 +66,8 @@ class MainActivity : BaseActivity(), private val searchSuggestionViewModel by viewModel() private lateinit var navHeaderBinding: NavigationHeaderBinding - private lateinit var drawerToggle: ActionBarDrawerToggle + private var drawerToggle: ActionBarDrawerToggle? = null + private var drawer: DrawerLayout? = null override val appBar: AppBarLayout get() = binding.appbar @@ -78,24 +76,31 @@ class MainActivity : BaseActivity(), super.onCreate(savedInstanceState) setContentView(ActivityMainBinding.inflate(layoutInflater)) navHeaderBinding = NavigationHeaderBinding.inflate(layoutInflater) - drawerToggle = ActionBarDrawerToggle( - this, - binding.drawer, - binding.toolbar, - R.string.open_menu, - R.string.close_menu - ) - drawerToggle.setHomeAsUpIndicator(ContextCompat.getDrawable(this, R.drawable.ic_arrow_back)) - drawerToggle.setToolbarNavigationClickListener { - binding.searchView.hideKeyboard() - onBackPressed() + 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, R.drawable.ic_arrow_back)) + setToolbarNavigationClickListener { + binding.searchView.hideKeyboard() + onBackPressed() + } + it.addDrawerListener(this) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + } } - binding.drawer.addDrawerListener(drawerToggle) - supportActionBar?.setDisplayHomeAsUpEnabled(true) with(binding.searchView) { onFocusChangeListener = this@MainActivity searchSuggestionListener = this@MainActivity + if (drawer == null) { + drawableStart = ContextCompat.getDrawable(context, R.drawable.ic_search) + } } with(binding.navigationView) { @@ -132,26 +137,27 @@ class MainActivity : BaseActivity(), override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) - drawerToggle.isDrawerIndicatorEnabled = - binding.drawer.getDrawerLockMode(GravityCompat.START) == DrawerLayout.LOCK_MODE_UNLOCKED + drawerToggle?.isDrawerIndicatorEnabled = + drawer?.getDrawerLockMode(GravityCompat.START) == DrawerLayout.LOCK_MODE_UNLOCKED } override fun onPostCreate(savedInstanceState: Bundle?) { super.onPostCreate(savedInstanceState) - drawerToggle.syncState() + drawerToggle?.syncState() } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - drawerToggle.onConfigurationChanged(newConfig) + drawerToggle?.onConfigurationChanged(newConfig) } override fun onBackPressed() { val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH) binding.searchView.clearFocus() when { - binding.drawer.isDrawerOpen(binding.navigationView) -> binding.drawer.closeDrawer( - binding.navigationView) + drawer?.isDrawerOpen(binding.navigationView) == true -> { + drawer?.closeDrawer(binding.navigationView) + } fragment != null -> supportFragmentManager.commit { remove(fragment) setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) @@ -162,7 +168,7 @@ class MainActivity : BaseActivity(), } override fun onOptionsItemSelected(item: MenuItem): Boolean { - return drawerToggle.onOptionsItemSelected(item) || when (item.itemId) { + return drawerToggle?.onOptionsItemSelected(item) == true || when (item.itemId) { else -> super.onOptionsItemSelected(item) } } @@ -208,7 +214,7 @@ class MainActivity : BaseActivity(), else -> return false } } - binding.drawer.closeDrawers() + drawer?.closeDrawers() return true } @@ -221,8 +227,10 @@ class MainActivity : BaseActivity(), leftMargin = insets.left + topMargin rightMargin = insets.right + topMargin } - binding.container.updateLayoutParams { - topMargin = -(binding.appbar.measureHeight()) + if (drawer != null) { + binding.container.updateLayoutParams { + topMargin = -(binding.appbar.measureHeight()) + } } } @@ -354,13 +362,13 @@ class MainActivity : BaseActivity(), } private fun onSearchOpened() { - binding.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) - drawerToggle.isDrawerIndicatorEnabled = false + drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) + drawerToggle?.isDrawerIndicatorEnabled = false } private fun onSearchClosed() { - binding.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) - drawerToggle.isDrawerIndicatorEnabled = true + drawer?.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) + drawerToggle?.isDrawerIndicatorEnabled = true } private fun onFirstStart() { diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt new file mode 100644 index 000000000..7276dab57 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt @@ -0,0 +1,20 @@ +package org.koitharu.kotatsu.utils.ext + +import android.view.View +import androidx.core.graphics.Insets + +fun Insets.getStart(view: View): Int { + return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + right + } else { + left + } +} + +fun Insets.getEnd(view: View): Int { + return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + left + } else { + right + } +} \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp/activity_details.xml b/app/src/main/res/layout-land/activity_details.xml similarity index 100% rename from app/src/main/res/layout-w600dp/activity_details.xml rename to app/src/main/res/layout-land/activity_details.xml diff --git a/app/src/main/res/layout-w720dp-land/activity_details.xml b/app/src/main/res/layout-w720dp-land/activity_details.xml new file mode 100644 index 000000000..bcf33f71a --- /dev/null +++ b/app/src/main/res/layout-w720dp-land/activity_details.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp-land/activity_main.xml b/app/src/main/res/layout-w720dp-land/activity_main.xml new file mode 100644 index 000000000..63b80ee61 --- /dev/null +++ b/app/src/main/res/layout-w720dp-land/activity_main.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 446d1ae2f..85c1bc8c4 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -3,7 +3,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/drawer" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".main.ui.MainActivity"> @@ -16,6 +15,7 @@ android:id="@id/container" android:layout_width="match_parent" android:layout_height="match_parent" + tools:layout="@layout/fragment_list" app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> + + + 760 + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 734613783..e2c130bde 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -21,4 +21,8 @@ + + + 600 + \ No newline at end of file From 52dbd70c2f99638fdce228abac1d8e65279fa242 Mon Sep 17 00:00:00 2001 From: Zakhar Timoshenko Date: Fri, 11 Mar 2022 21:50:49 +0300 Subject: [PATCH 03/84] Some adjustments --- .../koitharu/kotatsu/main/ui/MainActivity.kt | 6 ++-- .../res/layout-w720dp-land/activity_main.xml | 31 ++++++++----------- app/src/main/res/layout/activity_main.xml | 6 +--- 3 files changed, 16 insertions(+), 27 deletions(-) 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 474ea72ba..989159104 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 @@ -227,10 +227,8 @@ class MainActivity : BaseActivity(), leftMargin = insets.left + topMargin rightMargin = insets.right + topMargin } - if (drawer != null) { - binding.container.updateLayoutParams { - topMargin = -(binding.appbar.measureHeight()) - } + binding.container.updateLayoutParams { + topMargin = -(binding.appbar.measureHeight()) } } diff --git a/app/src/main/res/layout-w720dp-land/activity_main.xml b/app/src/main/res/layout-w720dp-land/activity_main.xml index 63b80ee61..df67c21a0 100644 --- a/app/src/main/res/layout-w720dp-land/activity_main.xml +++ b/app/src/main/res/layout-w720dp-land/activity_main.xml @@ -11,26 +11,33 @@ android:id="@+id/navigationView" android:layout_width="260dp" android:layout_height="match_parent" + android:fitsSystemWindows="false" app:drawerLayoutCornerSize="0dp" app:elevation="0dp" - android:fitsSystemWindows="false" app:menu="@menu/nav_drawer" /> + + + app:elevation="0dp" + app:liftOnScroll="false"> + android:focusableInTouchMode="true"> @@ -69,13 +71,6 @@ - - + app:contentInsetStartWithNavigation="0dp"> From 88a3589f1d9bc9bb154aee075d2406b85905e3bc Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 13 Mar 2022 09:53:31 +0200 Subject: [PATCH 04/84] Fix MainActivity insets --- .../koitharu/kotatsu/base/ui/BaseActivity.kt | 2 +- .../koitharu/kotatsu/base/ui/BaseFragment.kt | 11 ++++++-- .../kotatsu/details/ui/ChaptersFragment.kt | 16 ++--------- .../kotatsu/details/ui/DetailsActivity.kt | 22 ++++----------- .../kotatsu/details/ui/DetailsFragment.kt | 18 ++---------- .../kotatsu/list/ui/MangaListFragment.kt | 8 ++++-- .../koitharu/kotatsu/main/ui/MainActivity.kt | 28 ++++++++----------- .../main/ui/NavigationViewInsetsListener.kt | 24 ++++++++++++++++ .../res/layout-w720dp-land/activity_main.xml | 7 ++--- app/src/main/res/layout/activity_main.xml | 7 ++--- 10 files changed, 69 insertions(+), 74 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/main/ui/NavigationViewInsetsListener.kt diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt index c14d81151..a6fc2c466 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseActivity.kt @@ -31,7 +31,7 @@ abstract class BaseActivity : AppCompatActivity(), OnApplyWindo @Suppress("LeakingThis") protected val exceptionResolver = ExceptionResolver(this) - private var lastInsets: Insets = Insets.NONE + private var lastInsets: Insets? = null override fun onCreate(savedInstanceState: Bundle?) { val settings = get() diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt index 25ca6a503..f7b90fa01 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt @@ -9,6 +9,7 @@ import androidx.core.graphics.Insets import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.core.view.doOnNextLayout import androidx.fragment.app.Fragment import androidx.viewbinding.ViewBinding import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver @@ -23,7 +24,7 @@ abstract class BaseFragment : Fragment(), OnApplyWindowInsetsLi @Suppress("LeakingThis") protected val exceptionResolver = ExceptionResolver(this) - private var lastInsets: Insets = Insets.NONE + private var lastInsets: Insets? = null override fun onCreateView( inflater: LayoutInflater, @@ -37,12 +38,18 @@ abstract class BaseFragment : Fragment(), OnApplyWindowInsetsLi override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - lastInsets = Insets.NONE ViewCompat.setOnApplyWindowInsetsListener(view, this) + view.doOnNextLayout { + // Listener may not be called + if (lastInsets == null) { + onWindowInsetsChanged(Insets.NONE) + } + } } override fun onDestroyView() { viewBinding = null + lastInsets = null super.onDestroyView() } 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 4f4d1e1a5..4e66273a4 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 @@ -203,19 +203,9 @@ class ChaptersFragment : BaseFragment(), } override fun onWindowInsetsChanged(insets: Insets) { - val root = binding.root - if (root.parent is FragmentContainerView) { - binding.recyclerViewChapters.updatePaddingRelative( - end = insets.getEnd(root), - bottom = insets.bottom + binding.spinnerBranches.height, - ) - } else { - binding.recyclerViewChapters.updatePadding( - left = insets.left, - right = insets.right, - bottom = insets.bottom + binding.spinnerBranches.height, - ) - } + binding.recyclerViewChapters.updatePadding( + bottom = insets.bottom + binding.spinnerBranches.height, + ) } private fun onChaptersChanged(list: List) { 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 7bea427f3..10c22b67a 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 @@ -9,7 +9,6 @@ import android.view.MenuItem import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.view.ActionMode -import androidx.appcompat.widget.Toolbar import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.Insets import androidx.core.net.toFile @@ -96,22 +95,13 @@ class DetailsActivity : BaseActivity(), TabLayoutMediato binding.snackbar.updatePadding( bottom = insets.bottom ) - with(binding.toolbar) { - updatePadding( - left = insets.left, - right = insets.right - ) - updateLayoutParams { - topMargin = insets.top - } - } - val tabs = binding.tabs - if (tabs != null && tabs.parent !is Toolbar) { - tabs.updatePadding( - left = insets.left, - right = insets.right - ) + binding.toolbar.updateLayoutParams { + topMargin = insets.top } + binding.root.updatePadding( + left = insets.left, + right = insets.right + ) } private fun onNewChaptersChanged(newChapters: Int) { 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 47993dd60..0b271ef64 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 @@ -13,8 +13,6 @@ import androidx.core.net.toUri import androidx.core.text.parseAsHtml import androidx.core.view.isVisible import androidx.core.view.updatePadding -import androidx.core.view.updatePaddingRelative -import androidx.fragment.app.FragmentContainerView import coil.ImageLoader import coil.request.ImageRequest import coil.util.CoilUtils @@ -234,19 +232,9 @@ class DetailsFragment : BaseFragment(), View.OnClickList } override fun onWindowInsetsChanged(insets: Insets) { - val root = binding.root - if (root.parent is FragmentContainerView) { - root.updatePaddingRelative( - start = insets.getStart(root), - bottom = insets.bottom, - ) - } else { - root.updatePadding( - left = insets.left, - right = insets.right, - bottom = insets.bottom, - ) - } + binding.root.updatePadding( + bottom = insets.bottom, + ) } private fun bindTags(manga: Manga) { diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt index 70b2d8c93..796a479f3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt @@ -176,17 +176,19 @@ abstract class MangaListFragment : BaseFragment(), val headerHeight = (activity as? AppBarOwner)?.appBar?.measureHeight() ?: insets.top binding.root.updatePadding( left = insets.left, - right = insets.right + right = insets.right, ) if (activity is MainActivity) { + val topOffsetDiff = binding.recyclerView.paddingTop - headerHeight binding.recyclerView.updatePadding( top = headerHeight, - bottom = insets.bottom + bottom = insets.bottom, ) + binding.recyclerView.scrollBy(0, topOffsetDiff) binding.swipeRefreshLayout.setProgressViewOffset( true, headerHeight + resources.resolveDp(-72), - headerHeight + resources.resolveDp(10) + headerHeight + resources.resolveDp(10), ) } } 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 989159104..be471f458 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 @@ -10,13 +10,15 @@ import android.view.ViewGroup.MarginLayoutParams import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.content.ContextCompat import androidx.core.graphics.Insets -import androidx.core.view.* +import androidx.core.view.GravityCompat +import androidx.core.view.ViewCompat +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.google.android.material.appbar.AppBarLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -104,15 +106,7 @@ class MainActivity : BaseActivity(), } with(binding.navigationView) { - val menuView = - findViewById(com.google.android.material.R.id.design_navigation_view) - ViewCompat.setOnApplyWindowInsetsListener(navHeaderBinding.root) { v, insets -> - val systemWindowInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) - v.updatePadding(top = systemWindowInsets.top) - // NavigationView doesn't dispatch insets to the menu view, so pad the bottom here. - menuView.updatePadding(bottom = systemWindowInsets.bottom) - insets - } + ViewCompat.setOnApplyWindowInsetsListener(this, NavigationViewInsetsListener()) addHeaderView(navHeaderBinding.root) setNavigationItemSelectedListener(this@MainActivity) } @@ -219,14 +213,16 @@ class MainActivity : BaseActivity(), } override fun onWindowInsetsChanged(insets: Insets) { - binding.toolbarCard.updateLayoutParams { - topMargin = insets.top + resources.resolveDp(8) - } binding.fab.updateLayoutParams { bottomMargin = insets.bottom + topMargin - leftMargin = insets.left + topMargin - rightMargin = insets.right + topMargin } + binding.toolbarCard.updateLayoutParams { + topMargin = insets.top + bottomMargin + } + binding.root.updatePadding( + left = insets.left, + right = insets.right, + ) binding.container.updateLayoutParams { topMargin = -(binding.appbar.measureHeight()) } diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/NavigationViewInsetsListener.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/NavigationViewInsetsListener.kt new file mode 100644 index 000000000..f3a66a8fa --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/NavigationViewInsetsListener.kt @@ -0,0 +1,24 @@ +package org.koitharu.kotatsu.main.ui + +import android.view.View +import androidx.core.view.OnApplyWindowInsetsListener +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.updatePadding +import java.lang.ref.WeakReference +import com.google.android.material.R as materialR + +class NavigationViewInsetsListener : OnApplyWindowInsetsListener { + + private var menuViewRef: WeakReference? = null + + override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat { + val menuView = menuViewRef?.get() ?: v.findViewById(materialR.id.design_navigation_view).also { + menuViewRef = WeakReference(it) + } + val systemWindowInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) + v.updatePadding(top = systemWindowInsets.top) + // NavigationView doesn't dispatch insets to the menu view, so pad the bottom here. + menuView.updatePadding(bottom = systemWindowInsets.bottom) + return WindowInsetsCompat.CONSUMED + } +} \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp-land/activity_main.xml b/app/src/main/res/layout-w720dp-land/activity_main.xml index df67c21a0..c2b7c9e5c 100644 --- a/app/src/main/res/layout-w720dp-land/activity_main.xml +++ b/app/src/main/res/layout-w720dp-land/activity_main.xml @@ -32,16 +32,15 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:elevation="0dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" app:liftOnScroll="false"> @@ -29,10 +31,7 @@ android:id="@+id/toolbar_card" android:layout_width="match_parent" android:layout_height="48dp" - android:layout_marginStart="16dp" - android:layout_marginTop="8dp" - android:layout_marginEnd="16dp" - android:layout_marginBottom="8dp" + android:layout_marginVertical="8dp" android:background="@drawable/toolbar_background"> Date: Sun, 13 Mar 2022 17:10:34 +0200 Subject: [PATCH 05/84] Improve details activity layout --- .../core/parser/site/ComickFunRepository.kt | 2 +- .../kotatsu/details/ui/ChaptersFragment.kt | 40 ++-- .../kotatsu/details/ui/DetailsActivity.kt | 36 +++- .../koitharu/kotatsu/main/ui/MainActivity.kt | 1 + .../main/res/layout-land/activity_details.xml | 49 ----- .../main/res/layout-land/dialog_list_mode.xml | 81 +++++++ .../layout-w600dp-port/fragment_details.xml | 198 ------------------ .../fragment_details.xml | 44 ++-- .../layout-w720dp-land/activity_details.xml | 55 ----- .../res/layout-w720dp/activity_details.xml | 79 +++++++ .../res/layout-w720dp/fragment_chapters.xml | 28 +++ app/src/main/res/layout/activity_details.xml | 27 +-- app/src/main/res/values-w980dp/dimens.xml | 6 - app/src/main/res/values/dimens.xml | 7 - 14 files changed, 281 insertions(+), 372 deletions(-) delete mode 100644 app/src/main/res/layout-land/activity_details.xml create mode 100644 app/src/main/res/layout-land/dialog_list_mode.xml delete mode 100644 app/src/main/res/layout-w600dp-port/fragment_details.xml rename app/src/main/res/{layout-w600dp-land => layout-w600dp}/fragment_details.xml (92%) delete mode 100644 app/src/main/res/layout-w720dp-land/activity_details.xml create mode 100644 app/src/main/res/layout-w720dp/activity_details.xml create mode 100644 app/src/main/res/layout-w720dp/fragment_chapters.xml delete mode 100644 app/src/main/res/values-w980dp/dimens.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ComickFunRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ComickFunRepository.kt index bd0b858f1..3d19907c8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ComickFunRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/site/ComickFunRepository.kt @@ -77,7 +77,7 @@ class ComickFunRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposi altTitle = null, url = slug, publicUrl = "https://$domain/comic/$slug", - rating = jo.getDouble("rating").toFloat() / 10f, + rating = jo.optDouble("rating", -10.0).toFloat() / 10f, isNsfw = false, coverUrl = jo.getString("cover_url"), largeCoverUrl = null, 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 4e66273a4..0048e8fe5 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 @@ -4,13 +4,12 @@ import android.app.ActivityOptions import android.os.Bundle import android.view.* import android.widget.AdapterView +import android.widget.Spinner import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.graphics.Insets import androidx.core.view.isVisible import androidx.core.view.updatePadding -import androidx.core.view.updatePaddingRelative -import androidx.fragment.app.FragmentContainerView import androidx.recyclerview.widget.RecyclerView import com.google.android.material.divider.MaterialDividerItemDecoration import org.koin.androidx.viewmodel.ext.android.sharedViewModel @@ -26,7 +25,6 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.download.ui.service.DownloadService import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState -import org.koitharu.kotatsu.utils.ext.getEnd class ChaptersFragment : BaseFragment(), OnListItemClickListener, @@ -59,21 +57,9 @@ class ChaptersFragment : BaseFragment(), setHasFixedSize(true) adapter = chaptersAdapter } - val branchesAdapter = BranchesAdapter() - binding.spinnerBranches.adapter = branchesAdapter - binding.spinnerBranches.onItemSelectedListener = this - + binding.spinnerBranches?.let(::initSpinner) viewModel.isLoading.observe(viewLifecycleOwner, this::onLoadingStateChanged) viewModel.chapters.observe(viewLifecycleOwner, this::onChaptersChanged) - viewModel.branches.observe(viewLifecycleOwner) { - branchesAdapter.setItems(it) - binding.spinnerBranches.isVisible = it.size > 1 - } - viewModel.selectedBranchIndex.observe(viewLifecycleOwner) { - if (it != -1 && it != binding.spinnerBranches.selectedItemPosition) { - binding.spinnerBranches.setSelection(it) - } - } viewModel.isChaptersReversed.observe(viewLifecycleOwner) { activity?.invalidateOptionsMenu() } @@ -82,7 +68,7 @@ class ChaptersFragment : BaseFragment(), override fun onDestroyView() { chaptersAdapter = null selectionDecoration = null - binding.spinnerBranches.adapter = null + binding.spinnerBranches?.adapter = null super.onDestroyView() } @@ -169,7 +155,8 @@ class ChaptersFragment : BaseFragment(), } override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - viewModel.setSelectedBranch(binding.spinnerBranches.selectedItem as String?) + val spinner = binding.spinnerBranches ?: return + viewModel.setSelectedBranch(spinner.selectedItem as String?) } override fun onNothingSelected(parent: AdapterView<*>?) = Unit @@ -204,10 +191,25 @@ class ChaptersFragment : BaseFragment(), override fun onWindowInsetsChanged(insets: Insets) { binding.recyclerViewChapters.updatePadding( - bottom = insets.bottom + binding.spinnerBranches.height, + bottom = insets.bottom + (binding.spinnerBranches?.height ?: 0), ) } + private fun initSpinner(spinner: Spinner) { + val branchesAdapter = BranchesAdapter() + spinner.adapter = branchesAdapter + spinner.onItemSelectedListener = this + viewModel.branches.observe(viewLifecycleOwner) { + branchesAdapter.setItems(it) + spinner.isVisible = it.size > 1 + } + viewModel.selectedBranchIndex.observe(viewLifecycleOwner) { + if (it != -1 && it != spinner.selectedItemPosition) { + spinner.setSelection(it) + } + } + } + private fun onChaptersChanged(list: List) { chaptersAdapter?.items = list } 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 10c22b67a..24bf3130b 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 @@ -6,12 +6,16 @@ import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.view.View import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.Spinner import android.widget.Toast import androidx.appcompat.view.ActionMode import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.Insets import androidx.core.net.toFile +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.lifecycleScope @@ -32,6 +36,7 @@ import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.os.ShortcutsRepository import org.koitharu.kotatsu.databinding.ActivityDetailsBinding +import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter import org.koitharu.kotatsu.download.ui.service.DownloadService import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState @@ -40,7 +45,8 @@ import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.buildAlertDialog import org.koitharu.kotatsu.utils.ext.getDisplayMessage -class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy { +class DetailsActivity : BaseActivity(), TabLayoutMediator.TabConfigurationStrategy, + AdapterView.OnItemSelectedListener { private val viewModel by viewModel { parametersOf(MangaIntent(intent)) @@ -49,12 +55,16 @@ class DetailsActivity : BaseActivity(), TabLayoutMediato override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivityDetailsBinding.inflate(layoutInflater)) - supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.run { + setDisplayHomeAsUpEnabled(true) + setDisplayShowTitleEnabled(false) + } val pager = binding.pager if (pager != null) { pager.adapter = MangaDetailsAdapter(this) TabLayoutMediator(checkNotNull(binding.tabs), pager, this).attach() } + binding.spinnerBranches?.let(::initSpinner) viewModel.manga.observe(this, ::onMangaUpdated) viewModel.newChaptersCount.observe(this, ::onNewChaptersChanged) @@ -226,6 +236,13 @@ class DetailsActivity : BaseActivity(), TabLayoutMediato binding.pager?.isUserInputEnabled = true } + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + val spinner = binding.spinnerBranches ?: return + viewModel.setSelectedBranch(spinner.selectedItem as String?) + } + + override fun onNothingSelected(parent: AdapterView<*>?) = Unit + fun showChapterMissingDialog(chapterId: Long) { val remoteManga = viewModel.getRemoteManga() if (remoteManga == null) { @@ -252,6 +269,21 @@ class DetailsActivity : BaseActivity(), TabLayoutMediato }.show() } + private fun initSpinner(spinner: Spinner) { + val branchesAdapter = BranchesAdapter() + spinner.adapter = branchesAdapter + spinner.onItemSelectedListener = this + viewModel.branches.observe(this) { + branchesAdapter.setItems(it) + spinner.isVisible = it.size > 1 + } + viewModel.selectedBranchIndex.observe(this) { + if (it != -1 && it != spinner.selectedItemPosition) { + spinner.setSelection(it) + } + } + } + companion object { fun newIntent(context: Context, manga: Manga): Intent { 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 be471f458..b8d8cb999 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 @@ -209,6 +209,7 @@ class MainActivity : BaseActivity(), } } drawer?.closeDrawers() + appBar.setExpanded(true) return true } diff --git a/app/src/main/res/layout-land/activity_details.xml b/app/src/main/res/layout-land/activity_details.xml deleted file mode 100644 index 6bf664eea..000000000 --- a/app/src/main/res/layout-land/activity_details.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/dialog_list_mode.xml b/app/src/main/res/layout-land/dialog_list_mode.xml new file mode 100644 index 000000000..f103a0ac3 --- /dev/null +++ b/app/src/main/res/layout-land/dialog_list_mode.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp-port/fragment_details.xml b/app/src/main/res/layout-w600dp-port/fragment_details.xml deleted file mode 100644 index c42301de6..000000000 --- a/app/src/main/res/layout-w600dp-port/fragment_details.xml +++ /dev/null @@ -1,198 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp-land/fragment_details.xml b/app/src/main/res/layout-w600dp/fragment_details.xml similarity index 92% rename from app/src/main/res/layout-w600dp-land/fragment_details.xml rename to app/src/main/res/layout-w600dp/fragment_details.xml index 55df1bb95..d751d0c6c 100644 --- a/app/src/main/res/layout-w600dp-land/fragment_details.xml +++ b/app/src/main/res/layout-w600dp/fragment_details.xml @@ -17,8 +17,8 @@ android:id="@+id/imageView_cover" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="16dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" android:foreground="?selectableItemBackground" android:scaleType="centerCrop" android:transitionName="cover" @@ -35,9 +35,9 @@ android:id="@+id/textView_title" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="16dp" - android:layout_marginEnd="16dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="3" android:textAppearance="?attr/textAppearanceHeadlineSmall" @@ -50,9 +50,9 @@ android:id="@+id/textView_subtitle" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" - android:layout_marginTop="6dp" - android:layout_marginEnd="16dp" + android:layout_marginStart="8dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="8dp" android:ellipsize="end" android:maxLines="3" android:textAppearance="?attr/textAppearanceBodyMedium" @@ -65,11 +65,11 @@ android:id="@+id/textView_author" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="12dp" + android:layout_marginStart="6dp" android:layout_marginTop="2dp" - android:layout_marginEnd="12dp" + android:layout_marginEnd="6dp" android:background="@drawable/list_selector" - android:padding="4dp" + android:padding="2dp" android:singleLine="true" android:textColor="?attr/colorTertiary" android:textStyle="bold" @@ -84,9 +84,9 @@ android:id="@+id/textView_state" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="16dp" + android:layout_marginStart="8dp" android:layout_marginTop="4dp" - android:layout_marginEnd="16dp" + android:layout_marginEnd="8dp" android:drawablePadding="4dp" android:singleLine="true" android:textAppearance="?attr/textAppearanceBodySmall" @@ -96,12 +96,19 @@ tools:drawableStart="@drawable/ic_state_finished" tools:text="Finished" /> + + @@ -128,7 +135,6 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginTop="16dp" android:layout_marginEnd="16dp" android:enabled="false" android:text="@string/read" @@ -140,14 +146,6 @@ app:layout_constraintTop_toBottomOf="@id/info_layout" tools:text="@string/_continue" /> - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp/activity_details.xml b/app/src/main/res/layout-w720dp/activity_details.xml new file mode 100644 index 000000000..337b9ec77 --- /dev/null +++ b/app/src/main/res/layout-w720dp/activity_details.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp/fragment_chapters.xml b/app/src/main/res/layout-w720dp/fragment_chapters.xml new file mode 100644 index 000000000..386ce6fb0 --- /dev/null +++ b/app/src/main/res/layout-w720dp/fragment_chapters.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml index 5a723948f..8190e9c82 100644 --- a/app/src/main/res/layout/activity_details.xml +++ b/app/src/main/res/layout/activity_details.xml @@ -3,7 +3,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:id="@+id/coordinator" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".details.ui.DetailsActivity"> @@ -12,24 +11,28 @@ android:id="@+id/appbar" android:layout_width="match_parent" android:layout_height="wrap_content" - app:liftOnScroll="false" - app:elevation="0dp"> + app:elevation="0dp" + app:liftOnScroll="false"> + app:layout_scrollFlags="scroll|enterAlways" + tools:ignore="PrivateResource"> - + + + diff --git a/app/src/main/res/values-w980dp/dimens.xml b/app/src/main/res/values-w980dp/dimens.xml deleted file mode 100644 index 32b68f0c3..000000000 --- a/app/src/main/res/values-w980dp/dimens.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - 760 - - \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index e2c130bde..5003ad1fc 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -18,11 +18,4 @@ 36dp 48dp 16dp - - - - - - 600 - \ No newline at end of file From 8b295f6a9301c0c65e24e8668ecffabc0d00f925 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 13 Mar 2022 17:50:49 +0200 Subject: [PATCH 06/84] Fix dialogs width --- .../kotatsu/base/ui/BaseBottomSheet.kt | 2 +- .../kotatsu/reader/ui/ReaderActivity.kt | 10 ++- .../res/layout-w600dp/activity_reader.xml | 83 +++++++++++++++++++ app/src/main/res/values/themes.xml | 8 ++ 4 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/layout-w600dp/activity_reader.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt index d65e031fa..b2146c3ba 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseBottomSheet.kt @@ -42,7 +42,7 @@ abstract class BaseBottomSheet : BottomSheetDialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return if (resources.getBoolean(R.bool.is_tablet)) { - AppCompatDialog(context, theme) + AppCompatDialog(context, R.style.Theme_Kotatsu_Dialog) } else super.onCreateDialog(savedInstanceState) } diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt index cd15c2bfa..791bc316f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt @@ -241,7 +241,7 @@ class ReaderActivity : BaseFullscreenActivity(), rawX >= binding.root.width - gestureInsets.right || rawY >= binding.root.height - gestureInsets.bottom || binding.appbarTop.hasGlobalPoint(rawX, rawY) || - binding.appbarBottom.hasGlobalPoint(rawX, rawY) + binding.appbarBottom?.hasGlobalPoint(rawX, rawY) == true ) { false } else { @@ -318,11 +318,13 @@ class ReaderActivity : BaseFullscreenActivity(), if (binding.appbarTop.isVisible != isUiVisible) { val transition = TransitionSet() .setOrdering(TransitionSet.ORDERING_TOGETHER) - .addTransition(Slide(Gravity.BOTTOM).addTarget(binding.appbarBottom)) .addTransition(Slide(Gravity.TOP).addTarget(binding.appbarTop)) + binding.appbarBottom?.let { botomBar -> + transition.addTransition(Slide(Gravity.BOTTOM).addTarget(botomBar)) + } TransitionManager.beginDelayedTransition(binding.root, transition) binding.appbarTop.isVisible = isUiVisible - binding.appbarBottom.isVisible = isUiVisible + binding.appbarBottom?.isVisible = isUiVisible if (isUiVisible) { showSystemUI() } else { @@ -339,7 +341,7 @@ class ReaderActivity : BaseFullscreenActivity(), right = systemBars.right, left = systemBars.left ) - binding.appbarBottom.updatePadding( + binding.appbarBottom?.updatePadding( bottom = systemBars.bottom, right = systemBars.right, left = systemBars.left diff --git a/app/src/main/res/layout-w600dp/activity_reader.xml b/app/src/main/res/layout-w600dp/activity_reader.xml new file mode 100644 index 000000000..90a78c533 --- /dev/null +++ b/app/src/main/res/layout-w600dp/activity_reader.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index d1b29006e..a51ebf1e4 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -85,4 +85,12 @@ + From 3802bc146fc612d867881e6c267a25ca453f0b45 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 7 Mar 2022 15:04:03 +0200 Subject: [PATCH 07/84] Suggest tags on search --- app/src/main/AndroidManifest.xml | 2 + .../koitharu/kotatsu/base/ui/BaseFragment.kt | 10 --- .../kotatsu/base/ui/widgets/ChipsView.kt | 1 + .../koitharu/kotatsu/core/db/dao/TagsDao.kt | 39 ++++++++++ .../kotatsu/details/ui/DetailsFragment.kt | 18 +++-- .../ui/FavouritesContainerFragment.kt | 4 - .../kotatsu/history/ui/HistoryListFragment.kt | 4 - .../kotatsu/local/ui/LocalListFragment.kt | 4 - .../koitharu/kotatsu/main/ui/MainActivity.kt | 8 ++ .../remotelist/ui/RemoteListFragment.kt | 4 - .../search/domain/MangaSearchRepository.kt | 12 +++ .../kotatsu/search/ui/MangaListActivity.kt | 78 +++++++++++++++++++ .../kotatsu/search/ui/SearchFragment.kt | 2 - .../search/ui/global/GlobalSearchFragment.kt | 4 - .../ui/suggestion/SearchSuggestionListener.kt | 3 + .../suggestion/SearchSuggestionViewModel.kt | 19 ++++- .../adapter/SearchSuggestionAdapter.kt | 3 + .../adapter/SearchSuggestionTagsAD.kt | 23 ++++++ .../suggestion/model/SearchSuggestionItem.kt | 13 +++- .../suggestions/ui/SuggestionsFragment.kt | 4 - .../kotatsu/tracker/ui/FeedFragment.kt | 2 - .../layout/item_search_suggestion_tags.xml | 11 +++ 22 files changed, 218 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt create mode 100644 app/src/main/res/layout/item_search_suggestion_tags.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cb22f131e..d3762ec2a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -50,6 +50,8 @@ + diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt index 25ca6a503..0e7c863bb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BaseFragment.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.base.ui -import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -46,15 +45,6 @@ abstract class BaseFragment : Fragment(), OnApplyWindowInsetsLi super.onDestroyView() } - open fun getTitle(): CharSequence? = null - - override fun onAttach(context: Context) { - super.onAttach(context) - getTitle()?.let { - activity?.title = it - } - } - override fun onApplyWindowInsets(v: View?, insets: WindowInsetsCompat): WindowInsetsCompat { val newInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) if (newInsets != lastInsets) { diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt index 7d8730c33..c20b615cf 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/ChipsView.kt @@ -80,6 +80,7 @@ class ChipsView @JvmOverloads constructor( chip.setOnCloseIconClickListener(chipOnCloseListener) chip.setEnsureMinTouchTargetSize(false) chip.setOnClickListener(chipOnClickListener) + chip.isCheckable = false addView(chip) return chip } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt index 7f9655d19..c31307a24 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/dao/TagsDao.kt @@ -9,6 +9,45 @@ abstract class TagsDao { @Query("SELECT * FROM tags WHERE source = :source") abstract suspend fun findTags(source: String): List + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findPopularTags(limit: Int): List + + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + WHERE tags.source = :source + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findPopularTags(source: String, limit: Int): List + + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + WHERE tags.source = :source AND title LIKE :query + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findTags(source: String, query: String, limit: Int): List + + @Query( + """SELECT tags.* FROM tags + LEFT JOIN manga_tags ON tags.tag_id = manga_tags.tag_id + WHERE title LIKE :query + GROUP BY manga_tags.tag_id + ORDER BY COUNT(manga_id) DESC + LIMIT :limit""" + ) + abstract suspend fun findTags(query: String, limit: Int): List + @Insert(onConflict = OnConflictStrategy.IGNORE) abstract suspend fun insert(tag: TagEntity): Long 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 3cd328ce2..35d3b2e17 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 @@ -16,27 +16,26 @@ import androidx.core.view.updatePadding import coil.ImageLoader import coil.request.ImageRequest import coil.util.CoilUtils +import com.google.android.material.chip.Chip import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.sharedViewModel import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseFragment import org.koitharu.kotatsu.base.ui.widgets.ChipsView -import org.koitharu.kotatsu.core.model.Manga -import org.koitharu.kotatsu.core.model.MangaHistory -import org.koitharu.kotatsu.core.model.MangaSource -import org.koitharu.kotatsu.core.model.MangaState +import org.koitharu.kotatsu.core.model.* import org.koitharu.kotatsu.databinding.FragmentDetailsBinding import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesDialog import org.koitharu.kotatsu.image.ui.ImageActivity import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState +import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.utils.FileSize import org.koitharu.kotatsu.utils.ext.* class DetailsFragment : BaseFragment(), View.OnClickListener, - View.OnLongClickListener { + View.OnLongClickListener, ChipsView.OnChipClickListener { private val viewModel by sharedViewModel() private val coil by inject(mode = LazyThreadSafetyMode.NONE) @@ -54,6 +53,7 @@ class DetailsFragment : BaseFragment(), View.OnClickList binding.buttonRead.setOnLongClickListener(this) binding.imageViewCover.setOnClickListener(this) binding.textViewDescription.movementMethod = LinkMovementMethod.getInstance() + binding.chipsTags.onChipClickListener = this viewModel.manga.observe(viewLifecycleOwner, ::onMangaUpdated) viewModel.isLoading.observe(viewLifecycleOwner, ::onLoadingStateChanged) viewModel.favouriteCategories.observe(viewLifecycleOwner, ::onFavouriteChanged) @@ -231,6 +231,11 @@ class DetailsFragment : BaseFragment(), View.OnClickList } } + override fun onChipClick(chip: Chip, data: Any?) { + val tag = data as? MangaTag ?: return + startActivity(MangaListActivity.newIntent(requireContext(), tag)) + } + override fun onWindowInsetsChanged(insets: Insets) { binding.root.updatePadding( left = insets.left, @@ -244,7 +249,8 @@ class DetailsFragment : BaseFragment(), View.OnClickList manga.tags.map { tag -> ChipsView.ChipModel( title = tag.title, - icon = 0 + icon = 0, + data = tag, ) } ) diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt index 096d26cfd..edc8c8519 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/FavouritesContainerFragment.kt @@ -103,10 +103,6 @@ class FavouritesContainerFragment : BaseFragment(), else -> super.onOptionsItemSelected(item) } - override fun getTitle(): CharSequence? { - return context?.getString(R.string.favourites) - } - private fun onError(e: Throwable) { Snackbar.make(binding.pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show() } diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt index 550fab1a5..8b5aaa9ec 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListFragment.kt @@ -59,10 +59,6 @@ class HistoryListFragment : MangaListFragment() { } } - override fun getTitle(): CharSequence? { - return context?.getString(R.string.history) - } - override fun onCreatePopupMenu(inflater: MenuInflater, menu: Menu, data: Manga) { super.onCreatePopupMenu(inflater, menu, data) inflater.inflate(R.menu.popup_history, menu) diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt index e30120259..35c8e8986 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/LocalListFragment.kt @@ -92,10 +92,6 @@ class LocalListFragment : MangaListFragment(), ActivityResultCallback) { if (result.isEmpty()) return viewModel.importFiles(result) 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 3a3cea458..c1ed3e274 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 @@ -31,6 +31,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.prefs.AppSection import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.databinding.ActivityMainBinding @@ -42,6 +43,7 @@ import org.koitharu.kotatsu.local.ui.LocalListFragment import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.search.ui.SearchActivity +import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener @@ -258,6 +260,12 @@ class MainActivity : BaseActivity(), } } + override fun onTagClick(tag: MangaTag) { + startActivity( + MangaListActivity.newIntent(this, tag) + ) + } + override fun onQueryChanged(query: String) { searchSuggestionViewModel.onQueryChanged(query) } diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt index b1b2867e6..ef918db8e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListFragment.kt @@ -25,10 +25,6 @@ class RemoteListFragment : MangaListFragment() { viewModel.loadNextPage() } - override fun getTitle(): CharSequence { - return source.title - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.opt_list_remote, menu) diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index fc1f293c9..8027505f3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.withContext import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.core.model.SortOrder import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings @@ -79,6 +80,17 @@ class MangaSearchRepository( }.orEmpty() } + suspend fun getTagsSuggestion(query: String, limit: Int, source: MangaSource?): List { + return when { + query.isNotEmpty() && source != null -> db.tagsDao.findTags(source.name, "%$query%", limit) + query.isNotEmpty() -> db.tagsDao.findTags("%$query%", limit) + source != null -> db.tagsDao.findTags(source.name, limit) + else -> db.tagsDao.findPopularTags(limit) + }.map { + it.toMangaTag() + } + } + fun saveSearchQuery(query: String) { recentSuggestions.saveRecentQuery(query, null) } diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt new file mode 100644 index 000000000..bd6a77cda --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt @@ -0,0 +1,78 @@ +package org.koitharu.kotatsu.search.ui + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.ViewGroup +import androidx.core.graphics.Insets +import androidx.core.view.updateLayoutParams +import androidx.core.view.updatePadding +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import org.koin.androidx.viewmodel.ext.android.getViewModel +import org.koin.core.parameter.parametersOf +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.core.model.MangaTag +import org.koitharu.kotatsu.databinding.ActivitySearchGlobalBinding +import org.koitharu.kotatsu.list.ui.filter.FilterState +import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment +import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel + +class MangaListActivity : BaseActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(ActivitySearchGlobalBinding.inflate(layoutInflater)) + val tag = intent.getParcelableExtra(EXTRA_TAG) ?: run { + finishAfterTransition() + return + } + supportActionBar?.setDisplayHomeAsUpEnabled(true) + val fm = supportFragmentManager + if (fm.findFragmentById(R.id.container) == null) { + fm.commit { + val fragment = RemoteListFragment.newInstance(tag.source) + replace(R.id.container, fragment) + runOnCommit(ApplyFilterRunnable(fragment, tag)) + } + } + } + + override fun onWindowInsetsChanged(insets: Insets) { + with(binding.toolbar) { + updatePadding( + left = insets.left, + right = insets.right + ) + updateLayoutParams { + topMargin = insets.top + } + } + binding.container.updatePadding( + bottom = insets.bottom + ) + } + + private class ApplyFilterRunnable( + private val fragment: Fragment, + private val tag: MangaTag, + ) : Runnable { + + override fun run() { + val viewModel = fragment.getViewModel { + parametersOf(tag.source) + } + viewModel.applyFilter(FilterState(viewModel.filter.sortOrder, setOf(tag))) + } + } + + companion object { + + private const val EXTRA_TAG = "tag" + + fun newIntent(context: Context, tag: MangaTag) = + Intent(context, MangaListActivity::class.java) + .putExtra(EXTRA_TAG, tag) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt index 6f2621475..b74bd5d12 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchFragment.kt @@ -21,8 +21,6 @@ class SearchFragment : MangaListFragment() { viewModel.loadNextPage() } - override fun getTitle() = query - companion object { private const val ARG_QUERY = "query" diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt index 2f6ca1ae3..81fc4491a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/global/GlobalSearchFragment.kt @@ -17,10 +17,6 @@ class GlobalSearchFragment : MangaListFragment() { override fun onScrolledToEnd() = Unit - override fun getTitle(): CharSequence? { - return query - } - companion object { private const val ARG_QUERY = "query" diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt index d82a47bb0..f6b0c718d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionListener.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.search.ui.suggestion import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.core.model.MangaTag interface SearchSuggestionListener { @@ -11,4 +12,6 @@ interface SearchSuggestionListener { fun onQueryChanged(query: String) fun onClearSearchHistory() + + fun onTagClick(tag: MangaTag) } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt index 4317d5bdb..bb2002d82 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt @@ -7,15 +7,18 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* import kotlinx.coroutines.plus import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem private const val DEBOUNCE_TIMEOUT = 500L private const val SEARCH_THRESHOLD = 3 private const val MAX_MANGA_ITEMS = 3 -private const val MAX_QUERY_ITEMS = 120 -private const val MAX_SUGGESTION_ITEMS = MAX_MANGA_ITEMS + MAX_QUERY_ITEMS + 1 +private const val MAX_QUERY_ITEMS = 16 +private const val MAX_TAGS_ITEMS = 8 +private const val MAX_SUGGESTION_ITEMS = MAX_MANGA_ITEMS + MAX_QUERY_ITEMS + 2 class SearchSuggestionViewModel( private val repository: MangaSearchRepository, @@ -77,6 +80,10 @@ class SearchSuggestionViewModel( if (src != null) { result += SearchSuggestionItem.Header(src, isLocalSearch) } + val tags = repository.getTagsSuggestion(q, MAX_TAGS_ITEMS, src.takeIf { srcOnly }) + if (tags.isNotEmpty()) { + result.add(SearchSuggestionItem.Tags(mapTags(tags))) + } if (q.length >= SEARCH_THRESHOLD) { repository.getMangaSuggestion(q, MAX_MANGA_ITEMS, src.takeIf { srcOnly }) .mapTo(result) { @@ -89,4 +96,12 @@ class SearchSuggestionViewModel( suggestion.postValue(it) }.launchIn(viewModelScope + Dispatchers.Default) } + + private fun mapTags(tags: List): List = tags.map { tag -> + ChipsView.ChipModel( + icon = 0, + title = tag.title, + data = tag, + ) + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt index 06e6af858..5d1180d96 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt @@ -18,6 +18,7 @@ class SearchSuggestionAdapter( delegatesManager.addDelegate(ITEM_TYPE_MANGA, searchSuggestionMangaAD(coil, lifecycleOwner, listener)) .addDelegate(ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener)) .addDelegate(ITEM_TYPE_HEADER, searchSuggestionHeaderAD(listener)) + .addDelegate(ITEM_TYPE_TAGS, searchSuggestionTagsAD(listener)) } private class DiffCallback : DiffUtil.ItemCallback() { @@ -33,6 +34,7 @@ class SearchSuggestionAdapter( oldItem.query == newItem.query } oldItem is SearchSuggestionItem.Header && newItem is SearchSuggestionItem.Header -> true + oldItem is SearchSuggestionItem.Tags && newItem is SearchSuggestionItem.Tags -> true else -> false } @@ -47,5 +49,6 @@ class SearchSuggestionAdapter( const val ITEM_TYPE_MANGA = 0 const val ITEM_TYPE_QUERY = 1 const val ITEM_TYPE_HEADER = 2 + const val ITEM_TYPE_TAGS = 3 } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt new file mode 100644 index 000000000..3c070edad --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionTagsAD.kt @@ -0,0 +1,23 @@ +package org.koitharu.kotatsu.search.ui.suggestion.adapter + +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.widgets.ChipsView +import org.koitharu.kotatsu.core.model.MangaTag +import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener +import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem + +fun searchSuggestionTagsAD( + listener: SearchSuggestionListener, +) = adapterDelegate(R.layout.item_search_suggestion_tags) { + + val chipGroup = itemView as ChipsView + + chipGroup.onChipClickListener = ChipsView.OnChipClickListener { _, data -> + listener.onTagClick(data as? MangaTag ?: return@OnChipClickListener) + } + + bind { + chipGroup.setChips(item.tags) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index d2d9d04d7..e0dfcea2d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -1,21 +1,26 @@ package org.koitharu.kotatsu.search.ui.suggestion.model import kotlinx.coroutines.flow.MutableStateFlow +import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource -sealed class SearchSuggestionItem { +sealed interface SearchSuggestionItem { data class MangaItem( val manga: Manga, - ) : SearchSuggestionItem() + ) : SearchSuggestionItem data class RecentQuery( val query: String, - ) : SearchSuggestionItem() + ) : SearchSuggestionItem data class Header( val source: MangaSource, val isChecked: MutableStateFlow, - ) : SearchSuggestionItem() + ) : SearchSuggestionItem + + data class Tags( + val tags: List, + ) : SearchSuggestionItem } diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt index c70793405..507c2a142 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsFragment.kt @@ -46,10 +46,6 @@ class SuggestionsFragment : MangaListFragment() { override fun onScrolledToEnd() = Unit - override fun getTitle(): CharSequence? { - return context?.getString(R.string.suggestions) - } - companion object { fun newInstance() = SuggestionsFragment() diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt index 350979484..47a2168ee 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedFragment.kt @@ -35,8 +35,6 @@ class FeedFragment : BaseFragment(), PaginationScrollListen private var paddingVertical = 0 private var paddingHorizontal = 0 - override fun getTitle() = context?.getString(R.string.updates) - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setHasOptionsMenu(true) diff --git a/app/src/main/res/layout/item_search_suggestion_tags.xml b/app/src/main/res/layout/item_search_suggestion_tags.xml new file mode 100644 index 000000000..3a6d64787 --- /dev/null +++ b/app/src/main/res/layout/item_search_suggestion_tags.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file From 54dfc32455435851900735e8dd998956b13b9767 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 7 Mar 2022 15:29:52 +0200 Subject: [PATCH 08/84] Make SearchView clearable --- .../search/ui/widget/SearchEditText.kt | 29 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 3 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt index 350b82aab..02a10e453 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/widget/SearchEditText.kt @@ -1,14 +1,19 @@ package org.koitharu.kotatsu.search.ui.widget +import android.annotation.SuppressLint import android.content.Context import android.util.AttributeSet import android.view.KeyEvent +import android.view.MotionEvent import android.view.inputmethod.EditorInfo import androidx.annotation.AttrRes import androidx.appcompat.widget.AppCompatEditText +import androidx.core.content.ContextCompat import com.google.android.material.R import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener +private const val DRAWABLE_END = 2 + class SearchEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, @@ -16,6 +21,7 @@ class SearchEditText @JvmOverloads constructor( ) : AppCompatEditText(context, attrs, defStyleAttr) { var searchSuggestionListener: SearchSuggestionListener? = null + private val clearIcon = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_material) var query: String get() = text?.trim()?.toString().orEmpty() @@ -50,9 +56,32 @@ class SearchEditText @JvmOverloads constructor( lengthAfter: Int, ) { super.onTextChanged(text, start, lengthBefore, lengthAfter) + setCompoundDrawablesRelativeWithIntrinsicBounds( + null, + null, + if (text.isNullOrEmpty()) null else clearIcon, + null, + ) searchSuggestionListener?.onQueryChanged(query) } + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + if (event.action == MotionEvent.ACTION_UP) { + val drawable = compoundDrawablesRelative[DRAWABLE_END] ?: return super.onTouchEvent(event) + val isOnDrawable = drawable.isVisible && if (layoutDirection == LAYOUT_DIRECTION_RTL) { + event.x.toInt() in paddingLeft..(drawable.bounds.width() + paddingLeft) + } else { + event.x.toInt() in (width - drawable.bounds.width() - paddingRight)..(width - paddingRight) + } + if (isOnDrawable) { + text?.clear() + return true + } + } + return super.onTouchEvent(event) + } + override fun clearFocus() { super.clearFocus() text?.clear() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 446d1ae2f..359e1d52a 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -58,7 +58,8 @@ android:imeOptions="actionSearch" android:importantForAutofill="no" android:paddingBottom="1dp" - android:singleLine="true" /> + android:singleLine="true" + tools:drawableEnd="@drawable/ic_clear" /> From abc2fb0e40f461615adb022dbed16ca426061ee3 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 7 Mar 2022 15:38:43 +0200 Subject: [PATCH 09/84] Hide FAB on search suggestions --- .../org/koitharu/kotatsu/main/ui/MainActivity.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) 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 c1ed3e274..3d9332627 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 @@ -42,8 +42,8 @@ import org.koitharu.kotatsu.history.ui.HistoryListFragment import org.koitharu.kotatsu.local.ui.LocalListFragment import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment -import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.MangaListActivity +import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener @@ -358,17 +358,19 @@ class MainActivity : BaseActivity(), supportFragmentManager.beginTransaction() .replace(R.id.container, fragment, TAG_PRIMARY) .commit() - if (fragment is HistoryListFragment) binding.fab.show() else binding.fab.hide() + adjustFabVisibility(topFragment = fragment) } private fun onSearchOpened() { binding.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) drawerToggle.isDrawerIndicatorEnabled = false + adjustFabVisibility(isSearchOpened = true) } private fun onSearchClosed() { binding.drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) drawerToggle.isDrawerIndicatorEnabled = true + adjustFabVisibility(isSearchOpened = false) } private fun onFirstStart() { @@ -383,4 +385,11 @@ class MainActivity : BaseActivity(), } } } + + private fun adjustFabVisibility( + topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY), + isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true, + ) { + if (!isSearchOpened && topFragment is HistoryListFragment) binding.fab.show() else binding.fab.hide() + } } \ No newline at end of file From 25d52c5a619bffbc267ee9f84073fe1e943da831 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 11 Mar 2022 19:29:58 +0200 Subject: [PATCH 10/84] Enhance manga search suggestion --- .../list/ui/filter/FilterCoordinator.kt | 6 ++ .../remotelist/ui/RemoteListViewModel.kt | 4 + .../kotatsu/search/ui/MangaListActivity.kt | 3 +- .../SearchSuggestionItemCallback.kt | 4 +- .../suggestion/SearchSuggestionViewModel.kt | 66 ++++++++------ .../adapter/SearchSuggestionAdapter.kt | 26 ++---- .../adapter/SearchSuggestionMangaAD.kt | 46 ---------- .../adapter/SearchSuggestionsMangaListAD.kt | 89 ++++++++++++++++++ .../suggestion/model/SearchSuggestionItem.kt | 90 +++++++++++++++++-- .../kotatsu/utils/ScrollResetCallback.kt | 13 +++ .../kotatsu/utils/ext/CollectionExt.kt | 15 +++- app/src/main/res/layout/activity_main.xml | 4 +- .../layout/item_search_suggestion_manga.xml | 46 ---------- .../item_search_suggestion_manga_grid.xml | 39 ++++++++ .../item_search_suggestion_manga_list.xml | 15 ++++ app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/styles.xml | 10 ++- 17 files changed, 325 insertions(+), 153 deletions(-) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt delete mode 100644 app/src/main/res/layout/item_search_suggestion_manga.xml create mode 100644 app/src/main/res/layout/item_search_suggestion_manga_grid.xml create mode 100644 app/src/main/res/layout/item_search_suggestion_manga_list.xml diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt index 8e6075362..bbcd57ad5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/filter/FilterCoordinator.kt @@ -59,6 +59,12 @@ class FilterCoordinator( } } + fun setTags(tags: Set) { + currentState.update { oldValue -> + FilterState(oldValue.sortOrder, tags) + } + } + fun reset() { currentState.update { oldValue -> FilterState(oldValue.sortOrder, emptySet()) diff --git a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index 025dfd515..36f1bdcca 100644 --- a/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -110,6 +110,10 @@ class RemoteListViewModel( fun resetFilter() = filter.reset() + fun applyFilter(tags: Set) { + filter.setTags(tags) + } + private fun loadList(filterState: FilterState, append: Boolean) { if (loadingJob?.isActive == true) { return diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt index bd6a77cda..eb85bc179 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/MangaListActivity.kt @@ -15,7 +15,6 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.core.model.MangaTag import org.koitharu.kotatsu.databinding.ActivitySearchGlobalBinding -import org.koitharu.kotatsu.list.ui.filter.FilterState import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel @@ -63,7 +62,7 @@ class MangaListActivity : BaseActivity() { val viewModel = fragment.getViewModel { parametersOf(tag.source) } - viewModel.applyFilter(FilterState(viewModel.filter.sortOrder, setOf(tag))) + viewModel.applyFilter(setOf(tag)) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt index e983be92b..64e53dc6b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionItemCallback.kt @@ -2,7 +2,7 @@ package org.koitharu.kotatsu.search.ui.suggestion import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import org.koitharu.kotatsu.search.ui.suggestion.adapter.SearchSuggestionAdapter +import org.koitharu.kotatsu.search.ui.suggestion.adapter.SEARCH_SUGGESTION_ITEM_TYPE_QUERY import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem import org.koitharu.kotatsu.utils.ext.getItem @@ -18,7 +18,7 @@ class SearchSuggestionItemCallback( override fun getMovementFlags( recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, - ): Int = if (viewHolder.itemViewType == SearchSuggestionAdapter.ITEM_TYPE_QUERY) { + ): Int = if (viewHolder.itemViewType == SEARCH_SUGGESTION_ITEM_TYPE_QUERY) { movementFlags } else { 0 diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt index bb2002d82..02e7f2856 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/SearchSuggestionViewModel.kt @@ -2,10 +2,8 @@ package org.koitharu.kotatsu.search.ui.suggestion import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.plus import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.MangaSource @@ -14,11 +12,9 @@ import org.koitharu.kotatsu.search.domain.MangaSearchRepository import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem private const val DEBOUNCE_TIMEOUT = 500L -private const val SEARCH_THRESHOLD = 3 -private const val MAX_MANGA_ITEMS = 3 +private const val MAX_MANGA_ITEMS = 6 private const val MAX_QUERY_ITEMS = 16 private const val MAX_TAGS_ITEMS = 8 -private const val MAX_SUGGESTION_ITEMS = MAX_MANGA_ITEMS + MAX_QUERY_ITEMS + 2 class SearchSuggestionViewModel( private val repository: MangaSearchRepository, @@ -68,33 +64,49 @@ class SearchSuggestionViewModel( private fun setupSuggestion() { suggestionJob?.cancel() suggestionJob = combine( - query - .debounce(DEBOUNCE_TIMEOUT) - .mapLatest { q -> - q to repository.getQuerySuggestion(q, MAX_QUERY_ITEMS) - }, + query.debounce(DEBOUNCE_TIMEOUT), source, - isLocalSearch - ) { (q, queries), src, srcOnly -> - val result = ArrayList(MAX_SUGGESTION_ITEMS) + isLocalSearch, + ::Triple, + ).mapLatest { (searchQuery, src, srcOnly) -> + buildSearchSuggestion(searchQuery, src, srcOnly) + }.distinctUntilChanged() + .onEach { + suggestion.postValue(it) + }.launchIn(viewModelScope + Dispatchers.Default) + } + + private suspend fun buildSearchSuggestion( + searchQuery: String, + src: MangaSource?, + srcOnly: Boolean, + ): List = coroutineScope { + val queriesDeferred = async { + repository.getQuerySuggestion(searchQuery, MAX_QUERY_ITEMS) + } + val tagsDeferred = async { + repository.getTagsSuggestion(searchQuery, MAX_TAGS_ITEMS, src.takeIf { srcOnly }) + } + val mangaDeferred = async { + repository.getMangaSuggestion(searchQuery, MAX_MANGA_ITEMS, src.takeIf { srcOnly }) + } + + val tags = tagsDeferred.await() + val mangaList = mangaDeferred.await() + val queries = queriesDeferred.await() + + buildList(queries.size + 3) { if (src != null) { - result += SearchSuggestionItem.Header(src, isLocalSearch) + add(SearchSuggestionItem.Header(src, isLocalSearch)) } - val tags = repository.getTagsSuggestion(q, MAX_TAGS_ITEMS, src.takeIf { srcOnly }) if (tags.isNotEmpty()) { - result.add(SearchSuggestionItem.Tags(mapTags(tags))) + add(SearchSuggestionItem.Tags(mapTags(tags))) } - if (q.length >= SEARCH_THRESHOLD) { - repository.getMangaSuggestion(q, MAX_MANGA_ITEMS, src.takeIf { srcOnly }) - .mapTo(result) { - SearchSuggestionItem.MangaItem(it) - } + if (mangaList.isNotEmpty()) { + add(SearchSuggestionItem.MangaList(mangaList)) } - queries.mapTo(result) { SearchSuggestionItem.RecentQuery(it) } - result - }.onEach { - suggestion.postValue(it) - }.launchIn(viewModelScope + Dispatchers.Default) + queries.mapTo(this) { SearchSuggestionItem.RecentQuery(it) } + } } private fun mapTags(tags: List): List = tags.map { tag -> diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt index 5d1180d96..86a8d5283 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt @@ -8,6 +8,8 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem import kotlin.jvm.internal.Intrinsics +const val SEARCH_SUGGESTION_ITEM_TYPE_QUERY = 0 + class SearchSuggestionAdapter( coil: ImageLoader, lifecycleOwner: LifecycleOwner, @@ -15,10 +17,11 @@ class SearchSuggestionAdapter( ) : AsyncListDifferDelegationAdapter(DiffCallback()) { init { - delegatesManager.addDelegate(ITEM_TYPE_MANGA, searchSuggestionMangaAD(coil, lifecycleOwner, listener)) - .addDelegate(ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener)) - .addDelegate(ITEM_TYPE_HEADER, searchSuggestionHeaderAD(listener)) - .addDelegate(ITEM_TYPE_TAGS, searchSuggestionTagsAD(listener)) + delegatesManager + .addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener)) + .addDelegate(searchSuggestionHeaderAD(listener)) + .addDelegate(searchSuggestionTagsAD(listener)) + .addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener)) } private class DiffCallback : DiffUtil.ItemCallback() { @@ -27,15 +30,10 @@ class SearchSuggestionAdapter( oldItem: SearchSuggestionItem, newItem: SearchSuggestionItem, ): Boolean = when { - oldItem is SearchSuggestionItem.MangaItem && newItem is SearchSuggestionItem.MangaItem -> { - oldItem.manga.id == newItem.manga.id - } oldItem is SearchSuggestionItem.RecentQuery && newItem is SearchSuggestionItem.RecentQuery -> { oldItem.query == newItem.query } - oldItem is SearchSuggestionItem.Header && newItem is SearchSuggestionItem.Header -> true - oldItem is SearchSuggestionItem.Tags && newItem is SearchSuggestionItem.Tags -> true - else -> false + else -> oldItem.javaClass == newItem.javaClass } override fun areContentsTheSame( @@ -43,12 +41,4 @@ class SearchSuggestionAdapter( newItem: SearchSuggestionItem, ): Boolean = Intrinsics.areEqual(oldItem, newItem) } - - companion object { - - const val ITEM_TYPE_MANGA = 0 - const val ITEM_TYPE_QUERY = 1 - const val ITEM_TYPE_HEADER = 2 - const val ITEM_TYPE_TAGS = 3 - } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt deleted file mode 100644 index 2eed6b932..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionMangaAD.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.koitharu.kotatsu.search.ui.suggestion.adapter - -import androidx.lifecycle.LifecycleOwner -import coil.ImageLoader -import coil.request.Disposable -import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaBinding -import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener -import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem -import org.koitharu.kotatsu.utils.ext.enqueueWith -import org.koitharu.kotatsu.utils.ext.newImageRequest -import org.koitharu.kotatsu.utils.ext.textAndVisible - -fun searchSuggestionMangaAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, - listener: SearchSuggestionListener, -) = adapterDelegateViewBinding( - { inflater, parent -> ItemSearchSuggestionMangaBinding.inflate(inflater, parent, false) } -) { - - var imageRequest: Disposable? = null - - itemView.setOnClickListener { - listener.onMangaClick(item.manga) - } - - bind { - imageRequest?.dispose() - imageRequest = binding.imageViewCover.newImageRequest(item.manga.coverUrl) - .placeholder(R.drawable.ic_placeholder) - .fallback(R.drawable.ic_placeholder) - .error(R.drawable.ic_placeholder) - .allowRgb565(true) - .lifecycle(lifecycleOwner) - .enqueueWith(coil) - binding.textViewTitle.text = item.manga.title - binding.textViewSubtitle.textAndVisible = item.manga.altTitle - } - - onViewRecycled { - imageRequest?.dispose() - binding.imageViewCover.setImageDrawable(null) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt new file mode 100644 index 000000000..9c3eb37b1 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt @@ -0,0 +1,89 @@ +package org.koitharu.kotatsu.search.ui.suggestion.adapter + +import androidx.core.view.updatePadding +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import coil.ImageLoader +import coil.request.Disposable +import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration +import org.koitharu.kotatsu.core.model.Manga +import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaGridBinding +import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener +import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem +import org.koitharu.kotatsu.utils.ScrollResetCallback +import org.koitharu.kotatsu.utils.ext.enqueueWith +import org.koitharu.kotatsu.utils.ext.newImageRequest + +fun searchSuggestionMangaListAD( + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, + listener: SearchSuggestionListener, +) = adapterDelegate(R.layout.item_search_suggestion_manga_list) { + + val adapter = AsyncListDifferDelegationAdapter( + SuggestionMangaDiffCallback(), + searchSuggestionMangaGridAD(coil, lifecycleOwner, listener), + ) + val recyclerView = itemView as RecyclerView + recyclerView.adapter = adapter + val spacing = context.resources.getDimensionPixelOffset(R.dimen.search_suggestions_manga_spacing) + recyclerView.updatePadding( + left = recyclerView.paddingLeft - spacing, + right = recyclerView.paddingRight - spacing, + ) + recyclerView.addItemDecoration(SpacingItemDecoration(spacing)) + val scrollResetCallback = ScrollResetCallback(recyclerView) + + bind { + adapter.setItems(item.items, scrollResetCallback) + } +} + +private fun searchSuggestionMangaGridAD( + coil: ImageLoader, + lifecycleOwner: LifecycleOwner, + listener: SearchSuggestionListener, +) = adapterDelegateViewBinding( + { layoutInflater, parent -> ItemSearchSuggestionMangaGridBinding.inflate(layoutInflater, parent, false) } +) { + + var imageRequest: Disposable? = null + + itemView.setOnClickListener { + listener.onMangaClick(item) + } + + bind { + imageRequest?.dispose() + imageRequest = binding.imageViewCover.newImageRequest(item.coverUrl) + .placeholder(R.drawable.ic_placeholder) + .fallback(R.drawable.ic_placeholder) + .error(R.drawable.ic_placeholder) + .allowRgb565(true) + .lifecycle(lifecycleOwner) + .enqueueWith(coil) + binding.textViewTitle.text = item.title + } + + onViewRecycled { + imageRequest?.dispose() + binding.imageViewCover.setImageDrawable(null) + } +} + +private class SuggestionMangaDiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame(oldItem: Manga, newItem: Manga): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Manga, newItem: Manga): Boolean { + return oldItem.title == newItem.title && oldItem.coverUrl == newItem.coverUrl + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt index e0dfcea2d..369639462 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/suggestion/model/SearchSuggestionItem.kt @@ -4,23 +4,95 @@ import kotlinx.coroutines.flow.MutableStateFlow import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.core.model.Manga import org.koitharu.kotatsu.core.model.MangaSource +import org.koitharu.kotatsu.utils.ext.areItemsEquals sealed interface SearchSuggestionItem { - data class MangaItem( - val manga: Manga, - ) : SearchSuggestionItem + class MangaList( + val items: List, + ) : SearchSuggestionItem { - data class RecentQuery( + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaList + + return items.areItemsEquals(other.items) { a, b -> + a.title == b.title && a.coverUrl == b.coverUrl + } + } + + override fun hashCode(): Int { + return items.fold(0) { acc, t -> + var r = 31 * acc + t.title.hashCode() + r = 31 * r + t.coverUrl.hashCode() + r + } + } + } + + class RecentQuery( val query: String, - ) : SearchSuggestionItem + ) : SearchSuggestionItem { - data class Header( + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as RecentQuery + + if (query != other.query) return false + + return true + } + + override fun hashCode(): Int { + return query.hashCode() + } + } + + class Header( val source: MangaSource, val isChecked: MutableStateFlow, - ) : SearchSuggestionItem + ) : SearchSuggestionItem { - data class Tags( + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Header + + if (source != other.source) return false + if (isChecked !== other.isChecked) return false + + return true + } + + override fun hashCode(): Int { + var result = source.hashCode() + result = 31 * result + isChecked.hashCode() + return result + } + } + + class Tags( val tags: List, - ) : SearchSuggestionItem + ) : SearchSuggestionItem { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Tags + + if (tags != other.tags) return false + + return true + } + + override fun hashCode(): Int { + return tags.hashCode() + } + } } diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt new file mode 100644 index 000000000..5a279f4ec --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ScrollResetCallback.kt @@ -0,0 +1,13 @@ +package org.koitharu.kotatsu.utils + +import androidx.recyclerview.widget.RecyclerView +import java.lang.ref.WeakReference + +class ScrollResetCallback(recyclerView: RecyclerView) : Runnable { + + private val recyclerViewRef = WeakReference(recyclerView) + + override fun run() { + recyclerViewRef.get()?.scrollToPosition(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt index 441b87c31..58797833c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CollectionExt.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.utils.ext -import android.util.SparseArray import androidx.collection.ArrayMap import androidx.collection.ArraySet import androidx.collection.LongSparseArray @@ -82,4 +81,18 @@ fun MutableList.move(sourceIndex: Int, targetIndex: Int) { } else { Collections.rotate(subList(targetIndex, sourceIndex + 1), 1) } +} + +inline fun List.areItemsEquals(other: List, equals: (T, T) -> Boolean): Boolean { + if (size != other.size) { + return false + } + for (i in indices) { + val a = this[i] + val b = other[i] + if (!equals(a, b)) { + return false + } + } + return true } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 359e1d52a..682ca84a3 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -42,6 +42,7 @@ android:background="@null" android:focusable="true" android:focusableInTouchMode="true" + android:theme="@style/ThemeOverlay.Kotatsu.MainToolbar" app:contentInsetStartWithNavigation="0dp" app:titleTextAppearance="@style/TextAppearance.Kotatsu.PersistentToolbarTitle" app:titleTextColor="?android:colorControlNormal" @@ -52,6 +53,7 @@ style="@style/Widget.Kotatsu.SearchView" android:layout_width="match_parent" android:layout_height="match_parent" + android:layout_marginEnd="2dp" android:background="@null" android:gravity="center_vertical" android:hint="@string/search_manga" @@ -59,7 +61,7 @@ android:importantForAutofill="no" android:paddingBottom="1dp" android:singleLine="true" - tools:drawableEnd="@drawable/ic_clear" /> + tools:drawableEnd="@drawable/abc_ic_clear_material" /> diff --git a/app/src/main/res/layout/item_search_suggestion_manga.xml b/app/src/main/res/layout/item_search_suggestion_manga.xml deleted file mode 100644 index c0cf277fd..000000000 --- a/app/src/main/res/layout/item_search_suggestion_manga.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_search_suggestion_manga_grid.xml b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml new file mode 100644 index 000000000..dad0dddd8 --- /dev/null +++ b/app/src/main/res/layout/item_search_suggestion_manga_grid.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_search_suggestion_manga_list.xml b/app/src/main/res/layout/item_search_suggestion_manga_list.xml new file mode 100644 index 000000000..88bcf54fb --- /dev/null +++ b/app/src/main/res/layout/item_search_suggestion_manga_list.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 734613783..3f5cbbf6d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -19,6 +19,8 @@ 48dp 16dp + 124dp + 4dp \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f65fca080..08ab43dec 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + @@ -76,6 +76,10 @@ @bool/elevation_overlay_enabled + + + + + + @@ -98,6 +110,11 @@ ?android:attr/textColorSecondary + + diff --git a/app/src/main/res/xml/pref_about.xml b/app/src/main/res/xml/pref_about.xml index 985e8bbf5..7be3e5c8a 100644 --- a/app/src/main/res/xml/pref_about.xml +++ b/app/src/main/res/xml/pref_about.xml @@ -15,7 +15,7 @@ app:persistent="false" app:summary="@string/check_for_updates" /> - - - - Date: Sat, 19 Mar 2022 13:55:32 +0300 Subject: [PATCH 24/84] Minor UI fixes --- .../kotatsu/download/ui/DownloadsActivity.kt | 13 ++++--------- .../main/res/layout-w720dp-land/activity_main.xml | 1 - app/src/main/res/layout/activity_main.xml | 1 - .../res/layout/item_source_config_draggable.xml | 3 ++- app/src/main/res/layout/item_tracklog.xml | 3 ++- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt index d590a724d..e249e4dc5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/ui/DownloadsActivity.kt @@ -47,15 +47,10 @@ class DownloadsActivity : BaseActivity() { right = insets.right, bottom = insets.bottom ) - with(binding.toolbar) { - updatePadding( - left = insets.left, - right = insets.right - ) - updateLayoutParams { - topMargin = insets.top - } - } + binding.toolbar.updatePadding( + left = insets.left, + right = insets.right + ) } companion object { diff --git a/app/src/main/res/layout-w720dp-land/activity_main.xml b/app/src/main/res/layout-w720dp-land/activity_main.xml index 19eed7953..44a61bafd 100644 --- a/app/src/main/res/layout-w720dp-land/activity_main.xml +++ b/app/src/main/res/layout-w720dp-land/activity_main.xml @@ -80,7 +80,6 @@ android:contentDescription="@string/_continue" android:src="@drawable/ic_read_fill" android:visibility="gone" - app:backgroundTint="?attr/colorContainer" app:fabSize="normal" app:layout_anchor="@id/container" app:layout_anchorGravity="bottom|end" diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index aa669fba1..40137c696 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -74,7 +74,6 @@ android:contentDescription="@string/_continue" android:src="@drawable/ic_read_fill" android:visibility="gone" - app:backgroundTint="?attr/colorContainer" app:fabSize="normal" app:layout_anchor="@id/container" app:layout_anchorGravity="bottom|end" diff --git a/app/src/main/res/layout/item_source_config_draggable.xml b/app/src/main/res/layout/item_source_config_draggable.xml index dfdf1e04c..3af85fe59 100644 --- a/app/src/main/res/layout/item_source_config_draggable.xml +++ b/app/src/main/res/layout/item_source_config_draggable.xml @@ -43,7 +43,8 @@ android:ellipsize="end" android:singleLine="true" android:textAppearance="?attr/textAppearanceBodySmall" - tools:text="05.10.2021 • Scanlator" /> + tools:text="English" /> + Date: Sat, 19 Mar 2022 16:34:48 +0200 Subject: [PATCH 25/84] Fix redundant fragments in DetailsActivity --- app/build.gradle | 4 ++-- .../java/org/koitharu/kotatsu/KotatsuApp.kt | 1 + .../kotatsu/details/ui/ChaptersFragment.kt | 5 +---- .../kotatsu/details/ui/DetailsActivity.kt | 19 +++++++++++++++++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 22ec6381a..98082c6d4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,8 +97,8 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.squareup.okio:okio:3.0.0' - implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.1' - implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.1' + implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2' + implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2' implementation 'io.insert-koin:koin-android:3.1.5' implementation 'io.coil-kt:coil-base:1.4.0' diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt index 0111f7255..7e755507a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt +++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt @@ -93,6 +93,7 @@ class KotatsuApp : Application() { .detectWrongFragmentContainer() .detectRetainInstanceUsage() .detectSetUserVisibleHint() + .detectFragmentTagUsage() .build() } } \ 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 9a05baf54..d2812d4a6 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 @@ -75,10 +75,7 @@ class ChaptersFragment : BaseFragment(), override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) - // workaround: duplication after screen rotation - if (menu.findItem(R.id.action_reversed) == null) { - inflater.inflate(R.menu.opt_chapters, menu) - } + inflater.inflate(R.menu.opt_chapters, menu) } override fun onPrepareOptionsMenu(menu: Menu) { 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 b6ecb17ea..ee62e7926 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 @@ -18,6 +18,7 @@ import androidx.core.net.toFile import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding +import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.tabs.TabLayout @@ -63,6 +64,7 @@ class DetailsActivity : BaseActivity(), TabLayoutMediato pager.adapter = MangaDetailsAdapter(this) TabLayoutMediator(checkNotNull(binding.tabs), pager, this).attach() } + gcFragments() binding.spinnerBranches?.let(::initSpinner) viewModel.manga.observe(this, ::onMangaUpdated) @@ -293,6 +295,23 @@ class DetailsActivity : BaseActivity(), TabLayoutMediato } } + private fun gcFragments() { + val mustHaveId = binding.pager == null + val fm = supportFragmentManager + val fragmentsToRemove = fm.fragments.filter { f -> + (f.id == 0) == mustHaveId + } + if (fragmentsToRemove.isEmpty()) { + return + } + fm.commit { + setReorderingAllowed(true) + for (f in fragmentsToRemove) { + remove(f) + } + } + } + companion object { fun newIntent(context: Context, manga: Manga): Intent { From 6dc8ee5cf065c245028283c43304a609714cd9df Mon Sep 17 00:00:00 2001 From: Zakhar Timoshenko Date: Sun, 20 Mar 2022 11:53:54 +0300 Subject: [PATCH 26/84] Return `backgroundTint` to FAB --- app/src/main/res/layout-w720dp-land/activity_main.xml | 1 + app/src/main/res/layout/activity_main.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/res/layout-w720dp-land/activity_main.xml b/app/src/main/res/layout-w720dp-land/activity_main.xml index 44a61bafd..19eed7953 100644 --- a/app/src/main/res/layout-w720dp-land/activity_main.xml +++ b/app/src/main/res/layout-w720dp-land/activity_main.xml @@ -80,6 +80,7 @@ android:contentDescription="@string/_continue" android:src="@drawable/ic_read_fill" android:visibility="gone" + app:backgroundTint="?attr/colorContainer" app:fabSize="normal" app:layout_anchor="@id/container" app:layout_anchorGravity="bottom|end" diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 40137c696..aa669fba1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -74,6 +74,7 @@ android:contentDescription="@string/_continue" android:src="@drawable/ic_read_fill" android:visibility="gone" + app:backgroundTint="?attr/colorContainer" app:fabSize="normal" app:layout_anchor="@id/container" app:layout_anchorGravity="bottom|end" From fbdac9a7c0cb9486da845377413b0c5dcfc94fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Thu, 17 Mar 2022 06:28:27 +0100 Subject: [PATCH 27/84] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (265 of 265 strings) Translated using Weblate (Norwegian Bokmål) Currently translated at 100.0% (265 of 265 strings) Co-authored-by: Allan Nordhøy Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nb_NO/ Translation: Kotatsu/Strings --- app/src/main/res/values-ja/strings.xml | 13 +++++++------ app/src/main/res/values-nb-rNO/strings.xml | 21 +++++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index f788af22c..218a2bffd 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -37,8 +37,8 @@ ローカルストレージ 最新 評価 - ソート順に並べ替え - フォローシステム + ソート順に並べ替え + フォローシステム クリア すべての履歴を永久にクリアしますか? 削除 @@ -64,7 +64,7 @@ ページ テーマ インターネットに接続出来ませんでした - カテゴリー名を入力してください + カテゴリー名を入力してください アップデート キャッシュ ZIPファイルまたはCBZファイルを選択してください。 @@ -204,7 +204,7 @@ 最近の検索クエリを全て完全に削除しますか? 幅を合わせる 新しいチャプターを探しています - アプリを起動するためのパスワードを入力してください + アプリを起動するためのパスワードを入力してください データバックアップを作成 解決しました タップして再試行してください @@ -214,10 +214,10 @@ 翻訳 全てのデータが復元されました 復元 - 感謝の気持ち + 感謝の気持ち 準備中… 開始時に維持 - バックアップと復元 + バックアップと復元 著作権とライセンス リバース 選択した構成はこの漫画のために記憶されます @@ -261,4 +261,5 @@ NSFWのマンガを提案しない フィルターをリセット ジャンルを探す + Wi-Fiのみ使用 \ No newline at end of file diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index a98f796bb..6eed1bfad 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -84,8 +84,8 @@ Kilder annensteds hen Les mer Sikkerhetskopi lagret - Velkommen - Annet + Velkommen + Annet Kun søk på %s Bekreft Neste @@ -175,8 +175,8 @@ Navn Drakt Filter - Sorteringsrekkefølge - Vurdering + Sorteringsrekkefølge + Vurdering Nyeste Oppdatert Popularitet @@ -195,7 +195,7 @@ Ny kategori Favorittmerk dette Ingen favoritter enda - Ingen historikk enda + Ingen historikk enda Tøm historikk Prøv igjen Lukk @@ -223,7 +223,7 @@ Pågående Oversett dette programmet Oversettelse - Tilbakemelding + Tilbakemelding Emne på 4PDA Støtt utvikleren Hvis du liker programmet kan du kronerulle det på Yoomoney (tidligere Yandex.Money) @@ -259,4 +259,13 @@ Foreslå manga basert på vaner Påskrudd Avskrudd + Alltid + Forhåndsinnlast bilder + Tilbakestill filter + Finn sjanger + Aldri + Kun på Wi-Fi + Velg språkene du ønsker å lese manga. Du kan endre dette senere i innstillingene. + Innlogget som %s + 18+ \ No newline at end of file From 70006b3cf4399e958df12ccadd23337b6e864720 Mon Sep 17 00:00:00 2001 From: Luiz-bro Date: Thu, 17 Mar 2022 06:28:27 +0100 Subject: [PATCH 28/84] Translated using Weblate (Portuguese) Currently translated at 100.0% (266 of 266 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (265 of 265 strings) Co-authored-by: Luiz-bro Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/ Translation: Kotatsu/Strings --- app/src/main/res/values-pt/strings.xml | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 98d905a31..cd325765b 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -17,7 +17,7 @@ Limpar histórico Nada encontrado Sem histórico ainda - Ainda não há favoritos + Ainda não há favoritos Favoritar isso Adicionar Nomeie a categoria @@ -33,8 +33,8 @@ Nome Populares Avaliação - Ordem de classificação - Filtro + Ordem de classificação + Filtro Escuro Siga o sistema Páginas @@ -171,20 +171,20 @@ Digite a senha que será necessária quando o aplicativo for iniciado Confirme A senha deve ter 4 caracteres ou mais - Backup salvo + Backup salvo Alguns dispositivos têm um comportamento de sistema diferente, o que pode interromper as tarefas em segundo plano. Leia mais Ocultar a barra de ferramentas ao rolar Pesquise apenas em %s Outros - Bem vindo + Bem vindo Fontes disponíveis Fontes usadas Enfileirado Nenhum download ativo Você deve inserir um nome Traduzir esta aplicação - Comentar + Comentar Tópico no 4PDA Apoiar o desenvolvedor Se você gosta deste aplicativo, você pode enviar dinheiro através do Yoomoney (ex. Yandex.Money) @@ -259,4 +259,12 @@ Habilitado Desabilitado Não foi possível carregar a lista de gêneros + Somente em Wi-Fi + Selecione os idiomas que você deseja ler mangá. Você pode alterá-lo mais tarde nas configurações. + Encontrar gênero + Sempre + Redefinir filtro + Nunca + Pré-carregar páginas + Conectado como %s \ No newline at end of file From 8d958329b9327028f462ca9112afbe6d460c7c8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Thu, 17 Mar 2022 06:28:28 +0100 Subject: [PATCH 29/84] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (266 of 266 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (265 of 265 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/ Translation: Kotatsu/Strings --- app/src/main/res/values-tr/strings.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index d9fac3435..a29e1c9bd 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -264,6 +264,8 @@ Manga okumak istediğiniz dilleri seçin. Daha sonra ayarlardan değiştirebilirsiniz. Her zaman Hiçbir zaman - Yalnızca Wi-Fi kullanarak + Yalnızca Wi-Fi\'de Sayfaları önceden yükle + %s olarak oturum açıldı + 18+ \ No newline at end of file From fce73f6457c5ac1c6c709cd961a69414562e5b5e Mon Sep 17 00:00:00 2001 From: kuragehime Date: Thu, 17 Mar 2022 06:28:28 +0100 Subject: [PATCH 30/84] Translated using Weblate (Japanese) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (266 of 266 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (265 of 265 strings) Co-authored-by: kuragehime Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/ Translation: Kotatsu/Strings --- app/src/main/res/values-ja/strings.xml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 218a2bffd..d8ae4460e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -134,7 +134,7 @@ クリア アップデート フィードの更新はまもなく開始されます - 更新を探す + アップデートを確認 チェックしない Kotatsuを起動したときにパスワードを入力する パスワードを繰り返す @@ -158,14 +158,14 @@ パスワードが間違っています アプリを保護する 最新のアップデートを確認する - アップデートを見つける事が出来ませんでした + アップデートを確認する事が出来ませんでした 利用可能なアップデートはありません 右から左(←) 右から左(←)の読書を好む フィードバック 4PDAに関する話題 開発者をサポートします(Yoomoneyが開きます) - このアプリが気に入ったら、Yoomoney(Yandex.Moneyなど)からサポートできます + このアプリが気に入ったら、Yoomoney(Yandex.Moneyなど)から開発者をサポートできます ライセンス 承認済み %sへのログインはサポートされていません @@ -211,7 +211,7 @@ CAPTCHAが必要です デフォルト:%s 昨日 - 翻訳 + このアプリを翻訳 全てのデータが復元されました 復元 感謝の気持ち @@ -238,7 +238,7 @@ 名前を入力する必要があります 今日 この人達のお陰でKotatsuがより良くなりました - Kotatsuを翻訳する(Weblateのサイトを開きます) + Kotatsuを翻訳する(Weblateのサイトに移動します) 次のページ サイレント サインイン @@ -262,4 +262,10 @@ フィルターをリセット ジャンルを探す Wi-Fiのみ使用 + 決して + 漫画を読みたい言語を選択します。後で設定から変更することができます。 + 常に + ページのプリロード + %s としてログイン + 18歳以上 \ No newline at end of file From 4b49f7d7c12a13b27952a30d25cb245bfd71096c Mon Sep 17 00:00:00 2001 From: "J. Lavoie" Date: Thu, 17 Mar 2022 06:28:29 +0100 Subject: [PATCH 31/84] Translated using Weblate (Finnish) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (French) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Italian) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (German) Currently translated at 100.0% (267 of 267 strings) Translated using Weblate (Spanish) Currently translated at 99.6% (266 of 267 strings) Translated using Weblate (Finnish) Currently translated at 100.0% (266 of 266 strings) Translated using Weblate (French) Currently translated at 100.0% (266 of 266 strings) Translated using Weblate (Italian) Currently translated at 100.0% (266 of 266 strings) Translated using Weblate (German) Currently translated at 100.0% (266 of 266 strings) Translated using Weblate (Spanish) Currently translated at 98.4% (262 of 266 strings) Co-authored-by: J. Lavoie Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/ Translation: Kotatsu/Strings --- app/src/main/res/values-de/strings.xml | 2 ++ app/src/main/res/values-es/strings.xml | 21 +++++++++++++++------ app/src/main/res/values-fi/strings.xml | 2 ++ app/src/main/res/values-fr/strings.xml | 2 ++ app/src/main/res/values-it/strings.xml | 2 ++ app/src/main/res/values-pt/strings.xml | 1 + 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index e9454db18..45262d6c3 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -266,4 +266,6 @@ Genre finden Nur über WLAN Wählen Sie die Sprachen aus, in denen Sie Mangas lesen möchten. Sie können dies später in den Einstellungen ändern. + Angemeldet als %s + 18+ \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d0ac364f3..ed6307cdc 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -23,7 +23,7 @@ No se encontró nada Aún sin historial Leer - Aún no hay favoritos + Aún no hay favoritos Añadir a favoritos Nueva categoría Añadir @@ -43,8 +43,8 @@ Actualización Recientes Calificación - Orden de clasificación - Filtrar + Orden de clasificación + Filtrar Tema Claro Oscuro @@ -216,13 +216,13 @@ Falta un capítulo Traducir esta aplicación Comentarios - Traducción + Traducción Actualmente no hay descargas activas En la cola Leer más Copia de seguridad guardada correctamente - Bienvenido/a - Otro + Bienvenido/a + Otro Géneros Intenta reformular la consulta. ¿Realmente quiere eliminar todas las consultas de búsqueda recientes\? @@ -259,4 +259,13 @@ Desactivado No se puede cargar la lista de géneros Bloqueo en NSFW + Conectado como %s + Encontrar el género + Selecciona los idiomas en los que quieres leer el manga. Puedes cambiarlo más tarde en los ajustes. + Reiniciar el filtro + Nunca + 18+ + Siempre + Precargar las páginas + Sólo en Wi-Fi \ No newline at end of file diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index b94becd21..d6bb7e0db 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -266,4 +266,6 @@ Nollaa suodatin Etsi genre Valitse kielet, joilla haluat lukea mangaa. Voit muuttaa sitä myöhemmin asetuksissa. + Kirjautunut sisään nimellä %s + 18+ \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index fb43d0a09..107b212c6 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -266,4 +266,6 @@ Jamais Réinitialiser le filtre Sélectionnez les langues dans lesquelles vous souhaitez lire les mangas. Vous pouvez le changer plus tard dans les paramètres. + Connecté en tant que %s + 18+ \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b5a11a8ff..a2f3415a9 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -266,4 +266,6 @@ Precarica le pagine Solo usando il Wi-Fi Sempre + Registrato come %s + 18+ \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index cd325765b..a1aec7292 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -267,4 +267,5 @@ Nunca Pré-carregar páginas Conectado como %s + 18+ \ No newline at end of file From d1eb76d960337b20339a19cf106275b8c1486c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliaksiej=20Razuma=C5=AD?= Date: Thu, 17 Mar 2022 06:28:29 +0100 Subject: [PATCH 32/84] Translated using Weblate (Belarusian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 98.8% (265 of 268 strings) Co-authored-by: Aliaksiej Razumaŭ Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/be/ Translation: Kotatsu/Strings --- app/src/main/res/values-be/strings.xml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 7aba53101..760d6d3de 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -23,7 +23,7 @@ Нічога не знойдзена Гісторыя пустая Чытаць - Дадайце цікавую для вас мангу ў абранае, каб не страціць яе + Дадайце цікавую для вас мангу ў абранае, каб не страціць яе Дадаць у абраныя Стварыць катэгорыю Дадаць @@ -43,8 +43,8 @@ Абноўленая Новая Па рэйтынгу - Сартаванне - Фільтр + Сартаванне + Фільтр Тэма Светлая Цёмная @@ -204,11 +204,11 @@ Схаваць загаловак пры прагортцы Пошук толькі па %s Вы сапраўды хочаце выдаліць усе апошнія пошукавыя запыты\? - Падрабязна + Падрабязна Некаторыя вытворцы могуць змяняць паводзіны сістэмы, што можа парушаць выкананне фонавых задач. Рэзервовая копія паспяхова захавана Вітаю - Іншыя + Іншыя Вы можаце захаваць мангу з анлайн-крыніц або імпартаваць з файла. У вас яшчэ няма ніводнай захаванай мангі Вы можаце знайсці, што пачытаць, у бакавым меню. @@ -227,7 +227,7 @@ Падтрымаць распрацоўшчыка Тэма на 4PDA Зваротная сувязь - Дапамагчы з перакладам праграмы + Дапамагчы з перакладам праграмы Пераклад Вы выйдзеце з усіх крыніц, у якіх вы аўтарызаваны Аўтарызацыя на %s не падтрымліваецца @@ -259,4 +259,11 @@ Прапануеце мангу, заснаваную на вашых перавагах Уключыць прапановы Прапанова + Выберыце мову, на якой вы хочаце чытаць мангу. Вы зможаце змяніць гэта пазней. + Скінуць фільтр + Знайсці жанр + Заўсёды + 18+ + Ніколі + Толькі праз Wi-Fi \ No newline at end of file From c7cbe18afd98b3fe0ec489bd7661e0862ab32b5e Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 20 Mar 2022 17:15:11 +0200 Subject: [PATCH 33/84] Cleanup resources --- .../main/res/layout/item_checkable_multiple.xml | 14 -------------- app/src/main/res/layout/item_checkable_single.xml | 14 -------------- app/src/main/res/values-be/strings.xml | 6 ++---- app/src/main/res/values-de/strings.xml | 6 ++---- app/src/main/res/values-es/strings.xml | 6 ++---- app/src/main/res/values-fi/strings.xml | 6 ++---- app/src/main/res/values-fr/strings.xml | 6 ++---- app/src/main/res/values-it/strings.xml | 6 ++---- app/src/main/res/values-ja/strings.xml | 6 ++---- app/src/main/res/values-nb-rNO/strings.xml | 6 ++---- app/src/main/res/values-pt/strings.xml | 6 ++---- app/src/main/res/values-ru/strings.xml | 8 +++----- app/src/main/res/values-tr/strings.xml | 6 ++---- app/src/main/res/values/colors.xml | 1 - app/src/main/res/values/strings.xml | 6 ++---- app/src/main/res/values/styles.xml | 4 ---- 16 files changed, 25 insertions(+), 82 deletions(-) delete mode 100644 app/src/main/res/layout/item_checkable_multiple.xml delete mode 100644 app/src/main/res/layout/item_checkable_single.xml diff --git a/app/src/main/res/layout/item_checkable_multiple.xml b/app/src/main/res/layout/item_checkable_multiple.xml deleted file mode 100644 index 2feb5f5aa..000000000 --- a/app/src/main/res/layout/item_checkable_multiple.xml +++ /dev/null @@ -1,14 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_checkable_single.xml b/app/src/main/res/layout/item_checkable_single.xml deleted file mode 100644 index cec15830e..000000000 --- a/app/src/main/res/layout/item_checkable_single.xml +++ /dev/null @@ -1,14 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 760d6d3de..0ebe3659f 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -123,8 +123,7 @@ Недаступна Не атрымалася знайсці ніводнага даступнага сховішча Іншае сховішча - Бяспечнае злучэнне (HTTPS) - Гатова + Гатова Усе абраныя У гэтай катэгорыі нічога няма Прачытаць пазней @@ -201,8 +200,7 @@ Калі ласка, увядзіце пароль, які спатрэбіцца пры запуску праграмы Пацвердзіць Пароль павінен змяшчаць не менш за 4 сімвалы - Схаваць загаловак пры прагортцы - Пошук толькі па %s + Пошук толькі па %s Вы сапраўды хочаце выдаліць усе апошнія пошукавыя запыты\? Падрабязна Некаторыя вытворцы могуць змяняць паводзіны сістэмы, што можа парушаць выкананне фонавых задач. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 45262d6c3..67aea2e62 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -114,8 +114,7 @@ Anderes Möchtest du wirklich alle letzten Suchanfragen entfernen\? Nur auf %s suchen - Symbolleiste beim Blättern ausblenden - Mehr erfahren + Mehr erfahren Einige Hersteller können das Systemverhalten ändern, wodurch Hintergrundaufgaben unterbrochen werden können. Sicherung erfolgreich gespeichert Willkommen @@ -130,8 +129,7 @@ Diese Kategorie ist leer Alle Favoriten Fertig - Sichere Verbindung verwenden (HTTPS) - Sonstiger Speicherort + Sonstiger Speicherort Verfügbarer Speicher kann nicht gefunden werden Nicht verfügbar Seitenanimation diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ed6307cdc..ec9dd55e7 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -123,8 +123,7 @@ No disponible No hay almacenamiento disponible Otro almacenamiento - Utilizar conexión segura (HTTPS) - Aceptar + Aceptar Todos los favoritos Categoría vacía Leer más tarde @@ -228,8 +227,7 @@ ¿Realmente quiere eliminar todas las consultas de búsqueda recientes\? Terminado En curso - Ocultar la barra de herramientas al desplazarse - Este capítulo no aparece en su dispositivo. Descárguelo o léalo en línea. + Este capítulo no aparece en su dispositivo. Descárguelo o léalo en línea. Autorizado Tema sobre 4PDA Formato de la fecha diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index d6bb7e0db..45f5f2175 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -25,8 +25,7 @@ Muut Haluatko todella poistaa kaikki viimeaikaiset hakukyselyt\? Hae vain kohteesta %s - Piilota työkalupalkki selattaessa - Salasanan on oltava vähintään 4 merkkiä + Salasanan on oltava vähintään 4 merkkiä Vahvista Syötä salasana, joka vaaditaan, kun sovellus käynnistyy Seuraava @@ -98,8 +97,7 @@ Tämä luokka on tyhjä Kaikki suosikit Valmis - Käytä suojattua yhteyttä (HTTPS) - Muu tallennustila + Muu tallennustila Ei löydy vapaata tallennustilaa Ei saatavilla Mangan latauspaikka diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 107b212c6..2f9e644dd 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -8,8 +8,7 @@ Autre Supprimer définitivement toutes les requêtes de recherche récentes \? Rechercher uniquement sur %s - Masquer la barre d\'outils lors du défilement - Le mot de passe doit comporter 4 caractères ou plus + Le mot de passe doit comporter 4 caractères ou plus Confirmer Entrez un mot de passe pour démarrer l\'application avec Suivant @@ -86,8 +85,7 @@ Catégorie vide Tous les favoris Terminé - Utiliser une connexion sécurisée (HTTPS) - Autre stockage + Autre stockage Pas de stockage disponible Non disponible Dossier pour les téléchargements diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index a2f3415a9..f145c4237 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -71,8 +71,7 @@ Altro Vuoi davvero rimuovere tutte le ricerche recenti\? Cerca solo su %s - Nascondi la barra degli strumenti quando si scorre - La password deve essere di almeno 4 caratteri + La password deve essere di almeno 4 caratteri Conferma Inserisci la password che sarà richiesta all\'avvio dell\'applicazione Prossimo @@ -130,8 +129,7 @@ Questa categoria è vuota Tutti i preferiti Finito - Usa una connessione sicura (HTTPS) - Altro spazio di archiviazione + Altro spazio di archiviazione Impossibile trovare uno spazio di archiviazione disponibile Non disponibile Posizione per lo scaricamento dei manga diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index d8ae4460e..034264b51 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -120,8 +120,7 @@ 利用出来ません 使用可能なストレージがありません その他のストレージ - 安全な(HTTPS)接続を使用する - 完了 + 完了 全てのお気に入り 後で読む 更新 @@ -180,8 +179,7 @@ 新しいチャプターの確認:%1$dの%2$d 新しいカテゴリー 高さを合わせる - スクロール時にツールバーを非表示にする - チャプターがありません + チャプターがありません すべての更新履歴を完全に消去しますか? この不足した章をダウンロードしたり、オンラインで読んだりすることができます。 このコンテンツを表示するにはサインインしてください diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 6eed1bfad..f63b63f70 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -6,8 +6,7 @@ Lagre fra nettbaserte kilder, eller importer filer. Noen enheter endrer systemoppførselen, noe som kan ødelegge for bakgrunnsoppgaver. Fjern alle nylige søkespørringer for godt\? - Skjul verktøylinje under rulling - Passordet må være minst fire tegn + Passordet må være minst fire tegn Skriv inn passord å kreve for å starte programmet … og %1$d til Logg inn for å se dette innholdet @@ -44,8 +43,7 @@ Ny versjon: %s Nye kapitler av det du leser vises her Oppdatering - Bruk sikker (HTTPS)-tilkobling - Annen lagring + Annen lagring Hylle Lagre noe først Finn lesestoff i sidemenyen. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index a1aec7292..9f2070d23 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -174,8 +174,7 @@ Backup salvo Alguns dispositivos têm um comportamento de sistema diferente, o que pode interromper as tarefas em segundo plano. Leia mais - Ocultar a barra de ferramentas ao rolar - Pesquise apenas em %s + Pesquise apenas em %s Outros Bem vindo Fontes disponíveis @@ -220,8 +219,7 @@ Atualizações Todos os favoritos À espera de rede… - Usar conexão segura (HTTPS) - Resultados da pesquisa + Resultados da pesquisa Novos capítulos do que você está lendo são mostrados aqui Nova versão: %s Girar a tela diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 64f1bbf4f..a63bab333 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -48,7 +48,7 @@ Тема Светлая Тёмная - Следовать системе + Как в системе Страницы Очистить Очистить всю историю чтения полностью\? @@ -129,8 +129,7 @@ Недоступно Нет доступного хранилища Другое хранилище - Использовать безопасное (HTTPS) соединение - Готово + Готово Всё избранное Категория пуста Прочитать позже @@ -206,8 +205,7 @@ Введите пароль для запуска приложения Подтвердить Пароль должен состоять из 4 символов или более - Прятать заголовок при прокрутке - Поиск только по %s + Поиск только по %s Другие Добро пожаловать Удалить все последние поисковые запросы навсегда\? diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a29e1c9bd..89c0323cd 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -150,8 +150,7 @@ Çevrim içi kaynaklardan kaydedin veya dosyaları içe aktarın. Raf Son - Güvenli (HTTPS) bağlantı kullan - Boyut: %s + Boyut: %s İlgili Temizlendi Parola gir @@ -223,8 +222,7 @@ Veriler geri yüklendi, ancak hatalar var Tekrar denemek için dokunun İleri - Kaydırırken araç çubuğunu gizle - Diğer + Diğer CAPTCHA gerekli Tüm çerezler kaldırıldı Seçilen yapılandırma bu manga için hatırlanacak diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 9b0b3375f..7ae005635 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -18,7 +18,6 @@ #424242 #212121 #99000000 - #99000000 #66000000 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0eff2aeeb..d9e9aca80 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -130,8 +130,7 @@ Not available No available storage Other storage - Use secure (HTTPS) connection - Done + Done All favourites Empty category Read later @@ -208,8 +207,7 @@ Enter a password to start the app with Confirm The password must be 4 characters or more - Hide toolbar when scrolling - Search only on %s + Search only on %s Remove all recent search queries permanently? Other Welcome diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2b088ee38..2e235ca44 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -102,10 +102,6 @@ ?attr/colorOnBackground - - - - - + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/pref_suggestions.xml b/app/src/main/res/xml/pref_suggestions.xml index 0206aa4c2..224185a67 100644 --- a/app/src/main/res/xml/pref_suggestions.xml +++ b/app/src/main/res/xml/pref_suggestions.xml @@ -9,6 +9,12 @@ android:summary="@string/suggestions_summary" android:title="@string/suggestions_enable" /> + + Date: Fri, 8 Apr 2022 18:15:04 +0300 Subject: [PATCH 79/84] Add check to avoid TransactionTooLargeException --- .../core/model/parcelable/Parcelable.kt | 8 ++++-- .../core/model/parcelable/ParcelableManga.kt | 28 +++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt index 7b74c329c..67bb80044 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/Parcelable.kt @@ -4,7 +4,7 @@ import android.os.Parcel import androidx.core.os.ParcelCompat import org.koitharu.kotatsu.parsers.model.* -fun Manga.writeToParcel(out: Parcel, flags: Int) { +fun Manga.writeToParcel(out: Parcel, flags: Int, withChapters: Boolean) { out.writeLong(id) out.writeString(title) out.writeString(altTitle) @@ -18,7 +18,11 @@ fun Manga.writeToParcel(out: Parcel, flags: Int) { out.writeParcelable(ParcelableMangaTags(tags), flags) out.writeSerializable(state) out.writeString(author) - out.writeParcelable(chapters?.let(::ParcelableMangaChapters), flags) + if (withChapters) { + out.writeParcelable(chapters?.let(::ParcelableMangaChapters), flags) + } else { + out.writeString(null) + } out.writeSerializable(source) } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt index 44c0ae8f8..fd9b6cfa1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/parcelable/ParcelableManga.kt @@ -2,24 +2,34 @@ package org.koitharu.kotatsu.core.model.parcelable import android.os.Parcel import android.os.Parcelable -import android.util.Log -import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.parsers.model.Manga +// Limits to avoid TransactionTooLargeException +private const val MAX_SAFE_SIZE = 1024 * 512 // Assume that 512 kb is safe parcel size +private const val MAX_SAFE_CHAPTERS_COUNT = 40 // this is 100% safe + class ParcelableManga( val manga: Manga, ) : Parcelable { constructor(parcel: Parcel) : this(parcel.readManga()) - init { - if (BuildConfig.DEBUG && manga.chapters != null) { - Log.w("ParcelableManga", "Passing manga with chapters as Parcelable is dangerous!") - } - } - override fun writeToParcel(parcel: Parcel, flags: Int) { - manga.writeToParcel(parcel, flags) + val chapters = manga.chapters + if (chapters == null || chapters.size <= MAX_SAFE_CHAPTERS_COUNT) { + // fast path + manga.writeToParcel(parcel, flags, withChapters = true) + return + } + val tempParcel = Parcel.obtain() + manga.writeToParcel(tempParcel, flags, withChapters = true) + val size = tempParcel.dataSize() + if (size < MAX_SAFE_SIZE) { + parcel.appendFrom(tempParcel, 0, size) + } else { + manga.writeToParcel(parcel, flags, withChapters = false) + } + tempParcel.recycle() } override fun describeContents(): Int { From 2cd9ea19fdcc9f0f70ada50fc60a5e01e203ba04 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 9 Apr 2022 08:28:01 +0300 Subject: [PATCH 80/84] Update dependencies --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6515eeac4..b90b1bfdc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -65,12 +65,12 @@ android { } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) - implementation('com.github.nv95:kotatsu-parsers:3ea7e92e64') { + implementation('com.github.nv95:kotatsu-parsers:0ee689cd2f') { exclude group: 'org.json', module: 'json' } - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1' implementation 'androidx.core:core-ktx:1.7.0' implementation 'androidx.activity:activity-ktx:1.4.0' @@ -108,7 +108,7 @@ dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1' testImplementation 'io.insert-koin:koin-test-junit4:3.1.5' androidTestImplementation 'androidx.test:runner:1.4.0' From 934861322eb7d39823ac98781cae1c26512de5af Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 9 Apr 2022 18:35:07 +0300 Subject: [PATCH 81/84] Migrate to ExtendedFloatingActionButton --- app/build.gradle | 2 +- .../base/ui/util/ShrinkOnScrollBehavior.kt | 48 +++++++++++++++++++ .../koitharu/kotatsu/main/ui/MainActivity.kt | 11 ----- app/src/main/res/drawable/ic_read_fill.xml | 12 ----- .../res/layout-w720dp-land/activity_main.xml | 11 +++-- .../main/res/layout/activity_categories.xml | 1 + app/src/main/res/layout/activity_main.xml | 11 +++-- 7 files changed, 62 insertions(+), 34 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/util/ShrinkOnScrollBehavior.kt delete mode 100644 app/src/main/res/drawable/ic_read_fill.xml diff --git a/app/build.gradle b/app/build.gradle index b90b1bfdc..1225098d7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -100,7 +100,7 @@ dependencies { implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2' - implementation 'io.insert-koin:koin-android:3.1.5' + implementation 'io.insert-koin:koin-android:3.1.6' implementation 'io.coil-kt:coil-base:1.4.0' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.github.solkin:disk-lru-cache:1.4' diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ShrinkOnScrollBehavior.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ShrinkOnScrollBehavior.kt new file mode 100644 index 000000000..526c1e986 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/util/ShrinkOnScrollBehavior.kt @@ -0,0 +1,48 @@ +package org.koitharu.kotatsu.base.ui.util + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior +import androidx.core.view.ViewCompat +import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton + +class ShrinkOnScrollBehavior : Behavior { + + @Suppress("unused") constructor() : super() + @Suppress("unused") constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) + + override fun onStartNestedScroll( + coordinatorLayout: CoordinatorLayout, + child: ExtendedFloatingActionButton, + directTargetChild: View, + target: View, + axes: Int, + type: Int + ): Boolean { + return axes == ViewCompat.SCROLL_AXIS_VERTICAL + } + + override fun onNestedScroll( + coordinatorLayout: CoordinatorLayout, + child: ExtendedFloatingActionButton, + target: View, + dxConsumed: Int, + dyConsumed: Int, + dxUnconsumed: Int, + dyUnconsumed: Int, + type: Int, + consumed: IntArray + ) { + if (dyConsumed > 0) { + if (child.isExtended) { + child.shrink() + } + } else if (dyConsumed < 0) { + if (!child.isExtended) { + child.extend() + } + } + } +} \ No newline at end of file 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 091e6ae05..dafc6f28c 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 @@ -308,17 +308,6 @@ class MainActivity : private fun onLoadingStateChanged(isLoading: Boolean) { binding.fab.isEnabled = !isLoading - if (isLoading) { - binding.fab.setImageDrawable( - CircularProgressDrawable(this).also { - it.setColorSchemeColors(R.color.kotatsu_onPrimaryContainer) - it.strokeWidth = resources.resolveDp(3.5f) - it.start() - } - ) - } else { - binding.fab.setImageResource(R.drawable.ic_read_fill) - } } private fun updateSideMenu(remoteSources: List) { diff --git a/app/src/main/res/drawable/ic_read_fill.xml b/app/src/main/res/drawable/ic_read_fill.xml deleted file mode 100644 index c8f2f2010..000000000 --- a/app/src/main/res/drawable/ic_read_fill.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-w720dp-land/activity_main.xml b/app/src/main/res/layout-w720dp-land/activity_main.xml index abb9f34c9..10570546c 100644 --- a/app/src/main/res/layout-w720dp-land/activity_main.xml +++ b/app/src/main/res/layout-w720dp-land/activity_main.xml @@ -73,19 +73,20 @@ - diff --git a/app/src/main/res/layout/activity_categories.xml b/app/src/main/res/layout/activity_categories.xml index 022e3a397..ff28de4f7 100644 --- a/app/src/main/res/layout/activity_categories.xml +++ b/app/src/main/res/layout/activity_categories.xml @@ -63,6 +63,7 @@ app:icon="@drawable/ic_add" app:layout_anchor="@id/recyclerView" app:layout_anchorGravity="bottom|end" + app:layout_behavior="org.koitharu.kotatsu.base.ui.util.ShrinkOnScrollBehavior" app:layout_dodgeInsetEdges="bottom" /> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index d6b8abfe9..78c3d932c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -67,19 +67,20 @@ - From a6fcbefc7b9332e44d4c61d3f3c607c2a8f87e67 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sat, 9 Apr 2022 18:47:25 +0300 Subject: [PATCH 82/84] Update strings --- app/src/main/res/values-ru/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6a432f53a..e42e5ec06 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -268,4 +268,7 @@ Обновление рекомендаций Исключить жанры Укажите жанры, которые Вы не хотите видеть в рекомендациях + Удалить выбранную мангу с накопителя? + Удаление завершено + Загрузить выбранную мангу со всеми главами? Это может привести к большому расходу трафика и места на накопителе \ No newline at end of file From 7262b403f029ff4ff7a08b6a9ab1d0f615b9572d Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 10 Apr 2022 10:25:11 +0300 Subject: [PATCH 83/84] Hide reading fab if history is empty --- .../kotatsu/history/data/HistoryDao.kt | 3 +++ .../history/domain/HistoryRepository.kt | 12 +++++++++ .../koitharu/kotatsu/main/ui/MainActivity.kt | 26 +++++++++++++------ .../koitharu/kotatsu/main/ui/MainViewModel.kt | 9 ++++--- 4 files changed, 39 insertions(+), 11 deletions(-) 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 a2527f59e..062e8d898 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 @@ -38,6 +38,9 @@ abstract class HistoryDao { @Query("SELECT * FROM history WHERE manga_id = :id") abstract fun observe(id: Long): Flow + @Query("SELECT COUNT(*) FROM history") + abstract fun observeCount(): Flow + @Query("DELETE FROM history") abstract suspend fun clear() 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 99fa176f4..0701dafd0 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 @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.history.domain import androidx.room.withTransaction import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.MangaEntity @@ -26,6 +27,11 @@ class HistoryRepository( return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } } + suspend fun getLastOrNull(): Manga? { + val entity = db.historyDao.findAll(0, 1).firstOrNull() ?: return null + return entity.manga.toManga(entity.tags.mapToSet { it.toMangaTag() }) + } + fun observeAll(): Flow> { return db.historyDao.observeAll().mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) @@ -47,6 +53,12 @@ class HistoryRepository( } } + fun observeHasItems(): Flow { + return db.historyDao.observeCount() + .map { it > 0 } + .distinctUntilChanged() + } + suspend fun addOrUpdate(manga: Manga, chapterId: Long, page: Int, scroll: Int) { if (manga.isNsfw && settings.isHistoryExcludeNsfw) { return 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 fe5dce1d5..29f0c01ac 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 @@ -11,16 +11,12 @@ import androidx.appcompat.app.ActionBarDrawerToggle import androidx.appcompat.view.ActionMode import androidx.core.content.ContextCompat import androidx.core.graphics.Insets -import androidx.core.view.GravityCompat -import androidx.core.view.ViewCompat -import androidx.core.view.updateLayoutParams -import androidx.core.view.updatePadding +import androidx.core.view.* import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.commit import androidx.lifecycle.lifecycleScope -import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.google.android.material.appbar.AppBarLayout import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.navigation.NavigationView @@ -135,6 +131,7 @@ class MainActivity : viewModel.onOpenReader.observe(this, this::onOpenReader) viewModel.onError.observe(this, this::onError) viewModel.isLoading.observe(this, this::onLoadingStateChanged) + viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged) viewModel.remoteSources.observe(this, this::updateSideMenu) viewModel.isSuggestionsEnabled.observe(this, this::setSuggestionsEnabled) } @@ -313,14 +310,17 @@ class MainActivity : } private fun onError(e: Throwable) { - Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT) - .show() + Snackbar.make(binding.container, e.getDisplayMessage(resources), Snackbar.LENGTH_SHORT).show() } private fun onLoadingStateChanged(isLoading: Boolean) { binding.fab.isEnabled = !isLoading } + private fun onResumeEnabledChanged(isEnabled: Boolean) { + adjustFabVisibility(isResumeEnabled = isEnabled) + } + private fun updateSideMenu(remoteSources: List) { val submenu = binding.navigationView.menu.findItem(R.id.nav_remote_sources).subMenu submenu.removeGroup(R.id.group_remote_sources) @@ -397,10 +397,20 @@ class MainActivity : } private fun adjustFabVisibility( + isResumeEnabled: Boolean = viewModel.isResumeEnabled.value == true, topFragment: Fragment? = supportFragmentManager.findFragmentByTag(TAG_PRIMARY), isSearchOpened: Boolean = supportFragmentManager.findFragmentByTag(TAG_SEARCH)?.isVisible == true, ) { - if (!isSearchOpened && topFragment is HistoryListFragment) binding.fab.show() else binding.fab.hide() + val fab = binding.fab + if (isResumeEnabled && !isSearchOpened && topFragment is HistoryListFragment) { + if (!fab.isVisible) { + fab.show() + } + } else { + if (fab.isVisible) { + fab.hide() + } + } } private fun adjustDrawerLock() { diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt index a3fe2b263..f2b98d7e0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainViewModel.kt @@ -27,6 +27,10 @@ class MainViewModel( .map { settings.isSuggestionsEnabled } .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) + val isResumeEnabled = historyRepository + .observeHasItems() + .asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default) + val remoteSources = settings.observe() .filter { it == AppSettings.KEY_SOURCES_ORDER || it == AppSettings.KEY_SOURCES_HIDDEN } .onStart { emit("") } @@ -35,9 +39,8 @@ class MainViewModel( fun openLastReader() { launchLoadingJob { - val manga = historyRepository.getList(0, 1).firstOrNull() - ?: throw EmptyHistoryException() + val manga = historyRepository.getLastOrNull() ?: throw EmptyHistoryException() onOpenReader.call(manga) } } -} +} \ No newline at end of file From 5bed854b9c90c0cc89a560d6ca02e842cb4c06e1 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 10 Apr 2022 11:00:05 +0300 Subject: [PATCH 84/84] Refactor entity mapping --- .../base/domain/MangaDataRepository.kt | 17 ++--- .../kotatsu/core/db/entity/EntityMapping.kt | 76 +++++++++++++++++++ .../kotatsu/core/db/entity/MangaEntity.kt | 43 +---------- .../core/db/entity/MangaPrefsEntity.kt | 6 +- .../kotatsu/core/db/entity/MangaTagsEntity.kt | 3 +- .../kotatsu/core/db/entity/MangaWithTags.kt | 8 +- .../kotatsu/core/db/entity/TagEntity.kt | 23 +----- .../kotatsu/core/db/entity/TrackEntity.kt | 3 +- .../kotatsu/core/db/entity/TrackLogEntity.kt | 5 +- .../core/db/entity/TrackLogWithManga.kt | 16 +--- .../kotatsu/favourites/data/EntityMapping.kt | 14 ++++ .../data/FavouriteCategoryEntity.kt | 14 +--- .../favourites/domain/FavouritesRepository.kt | 19 +++-- .../kotatsu/history/data/EntityMapping.kt | 12 +++ .../kotatsu/history/data/HistoryEntity.kt | 16 +--- .../kotatsu/history/data/HistoryWithManga.kt | 2 +- .../history/domain/HistoryRepository.kt | 23 +++--- .../search/domain/MangaSearchRepository.kt | 2 + .../suggestions/data/SuggestionEntity.kt | 2 +- .../suggestions/data/SuggestionWithManga.kt | 2 +- .../domain/SuggestionRepository.kt | 13 ++-- .../tracker/domain/TrackingRepository.kt | 6 +- 22 files changed, 166 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/favourites/data/EntityMapping.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.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 21f9c9989..179d87868 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 @@ -2,22 +2,19 @@ package org.koitharu.kotatsu.base.domain import androidx.room.withTransaction import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.MangaEntity -import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity -import org.koitharu.kotatsu.core.db.entity.TagEntity +import org.koitharu.kotatsu.core.db.entity.* import org.koitharu.kotatsu.core.prefs.ReaderMode 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.util.mapToSet class MangaDataRepository(private val db: MangaDatabase) { suspend fun savePreferences(manga: Manga, mode: ReaderMode) { - val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) + val tags = manga.tags.toEntities() db.withTransaction { db.tagsDao.upsert(tags) - db.mangaDao.upsert(MangaEntity.from(manga), tags) + db.mangaDao.upsert(manga.toEntity(), tags) db.preferencesDao.upsert( MangaPrefsEntity( mangaId = manga.id, @@ -42,16 +39,14 @@ class MangaDataRepository(private val db: MangaDatabase) { } suspend fun storeManga(manga: Manga) { - val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) + val tags = manga.tags.toEntities() db.withTransaction { db.tagsDao.upsert(tags) - db.mangaDao.upsert(MangaEntity.from(manga), tags) + db.mangaDao.upsert(manga.toEntity(), tags) } } suspend fun findTags(source: MangaSource): Set { - return db.tagsDao.findTags(source.name).mapToSet { - it.toMangaTag() - } + return db.tagsDao.findTags(source.name).toMangaTags() } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt new file mode 100644 index 000000000..51cd77b2e --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/EntityMapping.kt @@ -0,0 +1,76 @@ +package org.koitharu.kotatsu.core.db.entity + +import java.util.* +import org.koitharu.kotatsu.core.model.TrackingLogItem +import org.koitharu.kotatsu.parsers.model.* +import org.koitharu.kotatsu.parsers.util.longHashCode +import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.parsers.util.toTitleCase + +// Entity to model + +fun TagEntity.toMangaTag() = MangaTag( + key = this.key, + title = this.title.toTitleCase(), + source = MangaSource.valueOf(this.source), +) + +fun Collection.toMangaTags() = mapToSet(TagEntity::toMangaTag) + +fun MangaEntity.toManga(tags: Set) = Manga( + id = this.id, + title = this.title, + altTitle = this.altTitle, + state = this.state?.let { MangaState.valueOf(it) }, + rating = this.rating, + isNsfw = this.isNsfw, + url = this.url, + publicUrl = this.publicUrl, + coverUrl = this.coverUrl, + largeCoverUrl = this.largeCoverUrl, + author = this.author, + source = MangaSource.valueOf(this.source), + tags = tags +) + +fun MangaWithTags.toManga() = manga.toManga(tags.toMangaTags()) + +fun TrackLogWithManga.toTrackingLogItem() = TrackingLogItem( + id = trackLog.id, + chapters = trackLog.chapters.split('\n').filterNot { x -> x.isEmpty() }, + manga = manga.toManga(tags.toMangaTags()), + createdAt = Date(trackLog.createdAt) +) + +// Model to entity + +fun Manga.toEntity() = MangaEntity( + id = id, + url = url, + publicUrl = publicUrl, + source = source.name, + largeCoverUrl = largeCoverUrl, + coverUrl = coverUrl, + altTitle = altTitle, + rating = rating, + isNsfw = isNsfw, + state = state?.name, + title = title, + author = author, +) + +fun MangaTag.toEntity() = TagEntity( + title = title, + key = key, + source = source.name, + id = "${key}_${source.name}".longHashCode() +) + +fun Collection.toEntities() = map(MangaTag::toEntity) + +// Other + +@Suppress("FunctionName") +fun SortOrder(name: String, fallback: SortOrder): SortOrder = runCatching { + SortOrder.valueOf(name) +}.getOrDefault(fallback) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt index 6716cfafd..c425f0e68 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaEntity.kt @@ -3,10 +3,6 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.parsers.model.MangaState -import org.koitharu.kotatsu.parsers.model.MangaTag @Entity(tableName = "manga") class MangaEntity( @@ -16,46 +12,11 @@ class MangaEntity( @ColumnInfo(name = "alt_title") val altTitle: String?, @ColumnInfo(name = "url") val url: String, @ColumnInfo(name = "public_url") val publicUrl: String, - @ColumnInfo(name = "rating") val rating: Float, //normalized value [0..1] or -1 + @ColumnInfo(name = "rating") val rating: Float, // normalized value [0..1] or -1 @ColumnInfo(name = "nsfw") val isNsfw: Boolean, @ColumnInfo(name = "cover_url") val coverUrl: String, @ColumnInfo(name = "large_cover_url") val largeCoverUrl: String?, @ColumnInfo(name = "state") val state: String?, @ColumnInfo(name = "author") val author: String?, @ColumnInfo(name = "source") val source: String -) { - - fun toManga(tags: Set = emptySet()) = Manga( - id = this.id, - title = this.title, - altTitle = this.altTitle, - state = this.state?.let { MangaState.valueOf(it) }, - rating = this.rating, - isNsfw = this.isNsfw, - url = this.url, - publicUrl = this.publicUrl, - coverUrl = this.coverUrl, - largeCoverUrl = this.largeCoverUrl, - author = this.author, - source = MangaSource.valueOf(this.source), - tags = tags - ) - - companion object { - - fun from(manga: Manga) = MangaEntity( - id = manga.id, - url = manga.url, - publicUrl = manga.publicUrl, - source = manga.source.name, - largeCoverUrl = manga.largeCoverUrl, - coverUrl = manga.coverUrl, - altTitle = manga.altTitle, - rating = manga.rating, - isNsfw = manga.isNsfw, - state = manga.state?.name, - title = manga.title, - author = manga.author - ) - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt index 99e94b25a..a09ccd884 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt @@ -6,13 +6,15 @@ import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( - tableName = "preferences", foreignKeys = [ + tableName = "preferences", + foreignKeys = [ ForeignKey( entity = MangaEntity::class, parentColumns = ["manga_id"], childColumns = ["manga_id"], onDelete = ForeignKey.CASCADE - )] + ) + ] ) class MangaPrefsEntity( @PrimaryKey(autoGenerate = false) diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt index ea7f0b3d2..d3ee401a6 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaTagsEntity.kt @@ -5,7 +5,8 @@ import androidx.room.Entity import androidx.room.ForeignKey @Entity( - tableName = "manga_tags", primaryKeys = ["manga_id", "tag_id"], foreignKeys = [ + tableName = "manga_tags", primaryKeys = ["manga_id", "tag_id"], + foreignKeys = [ ForeignKey( entity = MangaEntity::class, parentColumns = ["manga_id"], diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt index 524fe906d..8c35c376e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/MangaWithTags.kt @@ -3,7 +3,6 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation -import org.koitharu.kotatsu.parsers.util.mapToSet class MangaWithTags( @Embedded val manga: MangaEntity, @@ -12,8 +11,5 @@ class MangaWithTags( entityColumn = "tag_id", associateBy = Junction(MangaTagsEntity::class) ) - val tags: List -) { - - fun toManga() = manga.toManga(tags.mapToSet { it.toMangaTag() }) -} \ No newline at end of file + val tags: List, +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt index 150715f4a..fe813a02b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TagEntity.kt @@ -3,10 +3,6 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.parsers.model.MangaTag -import org.koitharu.kotatsu.parsers.util.longHashCode -import org.koitharu.kotatsu.parsers.util.toTitleCase @Entity(tableName = "tags") class TagEntity( @@ -15,21 +11,4 @@ class TagEntity( @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "key") val key: String, @ColumnInfo(name = "source") val source: String -) { - - fun toMangaTag() = MangaTag( - key = this.key, - title = this.title.toTitleCase(), - source = MangaSource.valueOf(this.source) - ) - - companion object { - - fun fromMangaTag(tag: MangaTag) = TagEntity( - title = tag.title, - key = tag.key, - source = tag.source.name, - id = "${tag.key}_${tag.source.name}".longHashCode() - ) - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt index 1b5c41492..91d65d82b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackEntity.kt @@ -6,7 +6,8 @@ import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( - tableName = "tracks", foreignKeys = [ + tableName = "tracks", + foreignKeys = [ ForeignKey( entity = MangaEntity::class, parentColumns = ["manga_id"], diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogEntity.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogEntity.kt index a4f608c4d..8bb8e61b4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogEntity.kt @@ -6,7 +6,8 @@ import androidx.room.ForeignKey import androidx.room.PrimaryKey @Entity( - tableName = "track_logs", foreignKeys = [ + tableName = "track_logs", + foreignKeys = [ ForeignKey( entity = MangaEntity::class, parentColumns = ["manga_id"], @@ -20,5 +21,5 @@ class TrackLogEntity( @ColumnInfo(name = "id") val id: Long = 0L, @ColumnInfo(name = "manga_id", index = true) val mangaId: Long, @ColumnInfo(name = "chapters") val chapters: String, - @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis() + @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(), ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt index 037b22ad8..7a6e145a4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/db/entity/TrackLogWithManga.kt @@ -3,10 +3,6 @@ package org.koitharu.kotatsu.core.db.entity import androidx.room.Embedded import androidx.room.Junction import androidx.room.Relation -import java.util.* -import org.koitharu.kotatsu.core.model.TrackingLogItem -import org.koitharu.kotatsu.parsers.util.mapToSet -import org.koitharu.kotatsu.utils.ext.mapToSet class TrackLogWithManga( @Embedded val trackLog: TrackLogEntity, @@ -20,13 +16,5 @@ class TrackLogWithManga( entityColumn = "tag_id", associateBy = Junction(MangaTagsEntity::class) ) - val tags: List -) { - - fun toTrackingLogItem() = TrackingLogItem( - id = trackLog.id, - chapters = trackLog.chapters.split('\n').filterNot { x -> x.isEmpty() }, - manga = manga.toManga(tags.mapToSet { x -> x.toMangaTag() }), - createdAt = Date(trackLog.createdAt) - ) -} \ No newline at end of file + val tags: List, +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/EntityMapping.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/EntityMapping.kt new file mode 100644 index 000000000..801f2566a --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/EntityMapping.kt @@ -0,0 +1,14 @@ +package org.koitharu.kotatsu.favourites.data + +import java.util.* +import org.koitharu.kotatsu.core.db.entity.SortOrder +import org.koitharu.kotatsu.core.model.FavouriteCategory +import org.koitharu.kotatsu.parsers.model.SortOrder + +fun FavouriteCategoryEntity.toFavouriteCategory(id: Long = categoryId.toLong()) = FavouriteCategory( + id = id, + title = title, + sortKey = sortKey, + order = SortOrder(order, SortOrder.NEWEST), + createdAt = Date(createdAt), +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt index b7a81e840..d2b0bc7ed 100644 --- a/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/favourites/data/FavouriteCategoryEntity.kt @@ -3,9 +3,6 @@ package org.koitharu.kotatsu.favourites.data import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey -import org.koitharu.kotatsu.core.model.FavouriteCategory -import org.koitharu.kotatsu.parsers.model.SortOrder -import java.util.* @Entity(tableName = "favourite_categories") class FavouriteCategoryEntity( @@ -15,13 +12,4 @@ class FavouriteCategoryEntity( @ColumnInfo(name = "sort_key") val sortKey: Int, @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "order") val order: String, -) { - - fun toFavouriteCategory(id: Long? = null) = FavouriteCategory( - id = id ?: categoryId.toLong(), - title = title, - sortKey = sortKey, - order = SortOrder.values().find { x -> x.name == order } ?: SortOrder.NEWEST, - createdAt = Date(createdAt), - ) -} \ No newline at end of file +) \ No newline at end of file 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 49156d9ce..5cb19fda5 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 @@ -6,36 +6,35 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.MangaEntity -import org.koitharu.kotatsu.core.db.entity.TagEntity +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.toFavouriteCategory import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.SortOrder -import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.utils.ext.mapItems class FavouritesRepository(private val db: MangaDatabase) { suspend fun getAllManga(): List { val entities = db.favouritesDao.findAll() - return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } + return entities.map { it.manga.toManga(it.tags.toMangaTags()) } } fun observeAll(order: SortOrder): Flow> { return db.favouritesDao.observeAll(order) - .mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } + .mapItems { it.manga.toManga(it.tags.toMangaTags()) } } suspend fun getManga(categoryId: Long): List { val entities = db.favouritesDao.findAll(categoryId) - return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } + return entities.map { it.manga.toManga(it.tags.toMangaTags()) } } fun observeAll(categoryId: Long, order: SortOrder): Flow> { return db.favouritesDao.observeAll(categoryId, order) - .mapItems { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } + .mapItems { it.manga.toManga(it.tags.toMangaTags()) } } fun observeAll(categoryId: Long): Flow> { @@ -95,9 +94,9 @@ class FavouritesRepository(private val db: MangaDatabase) { suspend fun addToCategory(categoryId: Long, mangas: Collection) { db.withTransaction { for (manga in mangas) { - val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) + val tags = manga.tags.toEntities() db.tagsDao.upsert(tags) - db.mangaDao.upsert(MangaEntity.from(manga), tags) + db.mangaDao.upsert(manga.toEntity(), tags) val entity = FavouriteEntity(manga.id, categoryId, System.currentTimeMillis()) db.favouritesDao.insert(entity) } @@ -122,7 +121,7 @@ class FavouritesRepository(private val db: MangaDatabase) { private fun observeOrder(categoryId: Long): Flow { return db.favouriteCategoriesDao.observe(categoryId) - .map { x -> SortOrder.values().find { it.name == x.order } ?: SortOrder.NEWEST } + .map { x -> SortOrder(x.order, SortOrder.NEWEST) } .distinctUntilChanged() } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.kt new file mode 100644 index 000000000..c03300b99 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/EntityMapping.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.history.data + +import java.util.* +import org.koitharu.kotatsu.core.model.MangaHistory + +fun HistoryEntity.toMangaHistory() = MangaHistory( + createdAt = Date(createdAt), + updatedAt = Date(updatedAt), + chapterId = chapterId, + page = page, + scroll = scroll.toInt() +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt index 770181595..24a487c60 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryEntity.kt @@ -5,11 +5,10 @@ import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey import org.koitharu.kotatsu.core.db.entity.MangaEntity -import org.koitharu.kotatsu.core.model.MangaHistory -import java.util.* @Entity( - tableName = "history", foreignKeys = [ + tableName = "history", + foreignKeys = [ ForeignKey( entity = MangaEntity::class, parentColumns = ["manga_id"], @@ -26,13 +25,4 @@ class HistoryEntity( @ColumnInfo(name = "chapter_id") val chapterId: Long, @ColumnInfo(name = "page") val page: Int, @ColumnInfo(name = "scroll") val scroll: Float, -) { - - fun toMangaHistory() = MangaHistory( - createdAt = Date(createdAt), - updatedAt = Date(updatedAt), - chapterId = chapterId, - page = page, - scroll = scroll.toInt() - ) -} \ No newline at end of file +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryWithManga.kt b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryWithManga.kt index 55f41adc6..eda8c0010 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryWithManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/data/HistoryWithManga.kt @@ -19,5 +19,5 @@ class HistoryWithManga( entityColumn = "tag_id", associateBy = Junction(MangaTagsEntity::class) ) - val tags: List + val tags: List, ) \ 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 0701dafd0..a88c8a82d 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 @@ -5,14 +5,13 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.MangaEntity -import org.koitharu.kotatsu.core.db.entity.TagEntity +import org.koitharu.kotatsu.core.db.entity.* import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.history.data.HistoryEntity +import org.koitharu.kotatsu.history.data.toMangaHistory import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag -import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.ext.mapItems @@ -24,25 +23,25 @@ class HistoryRepository( suspend fun getList(offset: Int, limit: Int = 20): List { val entities = db.historyDao.findAll(offset, limit) - return entities.map { it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) } + return entities.map { it.manga.toManga(it.tags.toMangaTags()) } } suspend fun getLastOrNull(): Manga? { val entity = db.historyDao.findAll(0, 1).firstOrNull() ?: return null - return entity.manga.toManga(entity.tags.mapToSet { it.toMangaTag() }) + return entity.manga.toManga(entity.tags.toMangaTags()) } fun observeAll(): Flow> { return db.historyDao.observeAll().mapItems { - it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) + it.manga.toManga(it.tags.toMangaTags()) } } fun observeAllWithHistory(): Flow> { return db.historyDao.observeAll().mapItems { MangaWithHistory( - it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)), - it.history.toMangaHistory() + it.manga.toManga(it.tags.toMangaTags()), + it.history.toMangaHistory(), ) } } @@ -63,10 +62,10 @@ class HistoryRepository( if (manga.isNsfw && settings.isHistoryExcludeNsfw) { return } - val tags = manga.tags.map(TagEntity.Companion::fromMangaTag) + val tags = manga.tags.toEntities() db.withTransaction { db.tagsDao.upsert(tags) - db.mangaDao.upsert(MangaEntity.from(manga), tags) + db.mangaDao.upsert(manga.toEntity(), tags) db.historyDao.upsert( HistoryEntity( mangaId = manga.id, @@ -74,7 +73,7 @@ class HistoryRepository( updatedAt = System.currentTimeMillis(), chapterId = chapterId, page = page, - scroll = scroll.toFloat() // we migrate to int, but decide to not update database + scroll = scroll.toFloat(), // we migrate to int, but decide to not update database ) ) trackingRepository.upsert(manga) @@ -106,7 +105,7 @@ class HistoryRepository( * Useful for replacing saved manga on deleting it with remove source */ suspend fun deleteOrSwap(manga: Manga, alternative: Manga?) { - if (alternative == null || db.mangaDao.update(MangaEntity.from(alternative)) <= 0) { + if (alternative == null || db.mangaDao.update(alternative.toEntity()) <= 0) { db.historyDao.delete(manga.id) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt index c8ed656e7..270238553 100644 --- a/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/search/domain/MangaSearchRepository.kt @@ -10,6 +10,8 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.isActive import kotlinx.coroutines.withContext import org.koitharu.kotatsu.core.db.MangaDatabase +import org.koitharu.kotatsu.core.db.entity.toManga +import org.koitharu.kotatsu.core.db.entity.toMangaTag import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.parsers.model.Manga diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionEntity.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionEntity.kt index 97212bf2e..c49daad43 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionEntity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionEntity.kt @@ -24,4 +24,4 @@ class SuggestionEntity( @FloatRange(from = 0.0, to = 1.0) @ColumnInfo(name = "relevance") val relevance: Float, @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis(), -) +) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionWithManga.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionWithManga.kt index 13aa11bec..c7fc3c15f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionWithManga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/data/SuggestionWithManga.kt @@ -19,5 +19,5 @@ data class SuggestionWithManga( entityColumn = "tag_id", associateBy = Junction(MangaTagsEntity::class) ) - val tags: List + val tags: List, ) \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt index 17dd71840..d334f31ef 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt @@ -3,10 +3,11 @@ package org.koitharu.kotatsu.suggestions.domain import androidx.room.withTransaction import kotlinx.coroutines.flow.Flow import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.core.db.entity.MangaEntity -import org.koitharu.kotatsu.core.db.entity.TagEntity +import org.koitharu.kotatsu.core.db.entity.toEntities +import org.koitharu.kotatsu.core.db.entity.toEntity +import org.koitharu.kotatsu.core.db.entity.toManga +import org.koitharu.kotatsu.core.db.entity.toMangaTags import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.suggestions.data.SuggestionEntity import org.koitharu.kotatsu.utils.ext.mapItems @@ -16,7 +17,7 @@ class SuggestionRepository( fun observeAll(): Flow> { return db.suggestionDao.observeAll().mapItems { - it.manga.toManga(it.tags.mapToSet(TagEntity::toMangaTag)) + it.manga.toManga(it.tags.toMangaTags()) } } @@ -32,9 +33,9 @@ class SuggestionRepository( db.withTransaction { db.suggestionDao.deleteAll() suggestions.forEach { x -> - val tags = x.manga.tags.map(TagEntity.Companion::fromMangaTag) + val tags = x.manga.tags.toEntities() db.tagsDao.upsert(tags) - db.mangaDao.upsert(MangaEntity.from(x.manga), tags) + db.mangaDao.upsert(x.manga.toEntity(), tags) db.suggestionDao.upsert( SuggestionEntity( mangaId = x.manga.id, diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt index dd0c4e9ec..661f68bba 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt @@ -4,6 +4,8 @@ import androidx.room.withTransaction import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.entity.TrackEntity import org.koitharu.kotatsu.core.db.entity.TrackLogEntity +import org.koitharu.kotatsu.core.db.entity.toManga +import org.koitharu.kotatsu.core.db.entity.toTrackingLogItem import org.koitharu.kotatsu.core.model.MangaTracking import org.koitharu.kotatsu.core.model.TrackingLogItem import org.koitharu.kotatsu.parsers.model.Manga @@ -22,10 +24,10 @@ class TrackingRepository( suspend fun getAllTracks(useFavourites: Boolean, useHistory: Boolean): List { val mangaList = ArrayList() if (useFavourites) { - db.favouritesDao.findAllManga().mapTo(mangaList) { it.toManga() } + db.favouritesDao.findAllManga().mapTo(mangaList) { it.toManga(emptySet()) } } if (useHistory) { - db.historyDao.findAllManga().mapTo(mangaList) { it.toManga() } + db.historyDao.findAllManga().mapTo(mangaList) { it.toManga(emptySet()) } } val tracks = db.tracksDao.findAll().groupBy { it.mangaId } return mangaList