From 6d0cd49db3e18cf0ad773c91806ee09478855a23 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Mon, 15 May 2023 18:37:25 +0300 Subject: [PATCH] Improve branch selection --- app/build.gradle | 16 ++-- .../base/ui/dialog/RecyclerViewAlertDialog.kt | 93 +++++++++++++++++++ .../base/ui/list/OnListItemClickListener.kt | 4 +- .../org/koitharu/kotatsu/core/model/Manga.kt | 25 +++-- .../core/parser/favicon/FaviconFetcher.kt | 17 +++- .../details/domain/BranchComparator.kt | 8 +- .../kotatsu/details/ui/DetailsActivity.kt | 37 ++++---- .../kotatsu/details/ui/DetailsMenuProvider.kt | 11 ++- .../kotatsu/details/ui/DetailsViewModel.kt | 20 ++-- .../kotatsu/details/ui/adapter/BranchAD.kt | 39 ++++++++ .../details/ui/adapter/BranchesAdapter.kt | 51 +++------- .../kotatsu/details/ui/model/MangaBranch.kt | 32 +++++++ .../koitharu/kotatsu/local/ui/ImportWorker.kt | 2 +- .../kotatsu/reader/ui/ReaderActivity.kt | 2 + .../kotatsu/reader/ui/ReaderManager.kt | 10 +- .../ui/config/ReaderConfigBottomSheet.kt | 6 +- .../{BaseReader.kt => BaseReaderFragment.kt} | 2 +- .../pager/reversed/ReversedReaderFragment.kt | 5 +- .../ui/pager/standard/PagerReaderFragment.kt | 5 +- .../ui/pager/webtoon/WebtoonReaderFragment.kt | 5 +- .../suggestions/ui/SuggestionsWorker.kt | 33 +++++-- .../kotatsu/tracker/work/TrackWorker.kt | 1 + .../kotatsu/utils/PreferenceIconTarget.kt | 22 ----- .../koitharu/kotatsu/utils/ext/FragmentExt.kt | 23 +++-- .../koitharu/kotatsu/utils/ext/InsetsExt.kt | 6 +- .../kotatsu/utils/ext/ThrowableExt.kt | 4 +- .../org/koitharu/kotatsu/utils/ext/ViewExt.kt | 6 ++ app/src/main/res/layout/item_branch.xml | 9 -- .../main/res/layout/item_branch_dropdown.xml | 11 --- app/src/main/res/values/strings.xml | 1 + build.gradle | 2 +- 31 files changed, 328 insertions(+), 180 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RecyclerViewAlertDialog.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchAD.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt rename app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/{BaseReader.kt => BaseReaderFragment.kt} (95%) delete mode 100644 app/src/main/java/org/koitharu/kotatsu/utils/PreferenceIconTarget.kt delete mode 100644 app/src/main/res/layout/item_branch.xml delete mode 100644 app/src/main/res/layout/item_branch_dropdown.xml diff --git a/app/build.gradle b/app/build.gradle index d275cece8..cbf886b85 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdkVersion 21 targetSdkVersion 33 - versionCode 543 - versionName '5.1-b2' + versionCode 544 + versionName '5.1' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -78,7 +78,7 @@ afterEvaluate { } dependencies { //noinspection GradleDependency - implementation('com.github.KotatsuApp:kotatsu-parsers:96b9ac36f3') { + implementation('com.github.KotatsuApp:kotatsu-parsers:cae7073f87') { exclude group: 'org.json', module: 'json' } @@ -86,7 +86,7 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.activity:activity-ktx:1.7.1' implementation 'androidx.fragment:fragment-ktx:1.5.7' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' @@ -122,8 +122,8 @@ dependencies { implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2' implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2' - implementation 'com.google.dagger:hilt-android:2.45' - kapt 'com.google.dagger:hilt-compiler:2.45' + implementation 'com.google.dagger:hilt-android:2.46.1' + kapt 'com.google.dagger:hilt-compiler:2.46.1' implementation 'androidx.hilt:hilt-work:1.0.0' kapt 'androidx.hilt:hilt-compiler:1.0.0' @@ -152,6 +152,6 @@ dependencies { androidTestImplementation 'androidx.room:room-testing:2.5.1' androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0' - androidTestImplementation 'com.google.dagger:hilt-android-testing:2.45' - kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.45' + androidTestImplementation 'com.google.dagger:hilt-android-testing:2.46.1' + kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.46.1' } diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RecyclerViewAlertDialog.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RecyclerViewAlertDialog.kt new file mode 100644 index 000000000..a2e28fd17 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/RecyclerViewAlertDialog.kt @@ -0,0 +1,93 @@ +package org.koitharu.kotatsu.base.ui.dialog + +import android.content.Context +import android.content.DialogInterface +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import androidx.core.view.updatePadding +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.hannesdorfmann.adapterdelegates4.AdapterDelegate +import com.hannesdorfmann.adapterdelegates4.AdapterDelegatesManager +import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter +import org.koitharu.kotatsu.R + +class RecyclerViewAlertDialog private constructor( + private val delegate: AlertDialog +) : DialogInterface by delegate { + + fun show() = delegate.show() + + class Builder(context: Context) { + + private val recyclerView = RecyclerView(context) + private val delegatesManager = AdapterDelegatesManager>() + private var items: List? = null + + private val delegate = MaterialAlertDialogBuilder(context) + .setView(recyclerView) + + init { + recyclerView.layoutManager = LinearLayoutManager(context) + recyclerView.updatePadding( + top = context.resources.getDimensionPixelOffset(R.dimen.list_spacing), + ) + recyclerView.clipToPadding = false + } + + fun setTitle(@StringRes titleResId: Int): Builder { + delegate.setTitle(titleResId) + return this + } + + fun setTitle(title: CharSequence): Builder { + delegate.setTitle(title) + return this + } + + fun setIcon(@DrawableRes iconId: Int): Builder { + delegate.setIcon(iconId) + return this + } + + fun setPositiveButton( + @StringRes textId: Int, + listener: DialogInterface.OnClickListener, + ): Builder { + delegate.setPositiveButton(textId, listener) + return this + } + + fun setNegativeButton( + @StringRes textId: Int, + listener: DialogInterface.OnClickListener? = null + ): Builder { + delegate.setNegativeButton(textId, listener) + return this + } + + fun setCancelable(isCancelable: Boolean): Builder { + delegate.setCancelable(isCancelable) + return this + } + + fun addAdapterDelegate(subject: AdapterDelegate>): Builder { + delegatesManager.addDelegate(subject) + return this + } + + fun setItems(list: List): Builder { + items = list + return this + } + + fun create(): RecyclerViewAlertDialog { + recyclerView.adapter = ListDelegationAdapter(delegatesManager).also { + it.items = items + } + return RecyclerViewAlertDialog(delegate.create()) + } + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt index f39b81d14..e61f85bb0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/list/OnListItemClickListener.kt @@ -2,9 +2,9 @@ package org.koitharu.kotatsu.base.ui.list import android.view.View -interface OnListItemClickListener { +fun interface OnListItemClickListener { fun onItemClick(item: I, view: View) fun onItemLongClick(item: I, view: View) = false -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt index f9720bb13..47ac983f3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/Manga.kt @@ -3,8 +3,8 @@ package org.koitharu.kotatsu.core.model import androidx.core.os.LocaleListCompat import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.util.mapToSet -import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.utils.ext.iterator fun Collection.ids() = mapToSet { it.id } @@ -35,15 +35,22 @@ fun Manga.getPreferredBranch(history: MangaHistory?): String? { } } val groups = ch.groupBy { it.branch } + if (groups.size == 1) { + return groups.keys.first() + } + val candidates = HashMap>(groups.size) for (locale in LocaleListCompat.getAdjustedDefault()) { - var language = locale.getDisplayLanguage(locale).toTitleCase(locale) - if (groups.containsKey(language)) { - return language - } - language = locale.getDisplayName(locale).toTitleCase(locale) - if (groups.containsKey(language)) { - return language + val displayLanguage = locale.getDisplayLanguage(locale) + val displayName = locale.getDisplayName(locale) + for (branch in groups.keys) { + if (branch != null && ( + branch.contains(displayLanguage, ignoreCase = true) || + branch.contains(displayName, ignoreCase = true) + ) + ) { + candidates[branch] = groups[branch] ?: continue + } } } - return groups.maxByOrNull { it.value.size }?.key + return candidates.ifEmpty { groups }.maxByOrNull { it.value.size }?.key } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt index ab403a43c..be0d0dcfc 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/favicon/FaviconFetcher.kt @@ -19,10 +19,13 @@ import okhttp3.Request import okhttp3.Response import okhttp3.ResponseBody import okhttp3.internal.closeQuietly +import okio.Closeable +import okio.buffer import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.RemoteMangaRepository import org.koitharu.kotatsu.local.data.CacheDir +import org.koitharu.kotatsu.local.data.util.withExtraCloseable import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.await import java.net.HttpURLConnection @@ -54,7 +57,9 @@ class FaviconFetcher( val icon = checkNotNull(favicons.find(sizePx)) { "No favicons found" } val response = loadIcon(icon.url, mangaSource) val responseBody = response.requireBody() - val source = writeToDiskCache(responseBody)?.toImageSource() ?: responseBody.toImageSource() + val source = writeToDiskCache(responseBody)?.toImageSource()?.also { + response.closeQuietly() + } ?: responseBody.toImageSource(response) return SourceResult( source = source, mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(icon.type), @@ -71,7 +76,7 @@ class FaviconFetcher( options.tags.asMap().forEach { request.tag(it.key as Class, it.value) } val response = okHttpClient.newCall(request.build()).await() if (!response.isSuccessful && response.code != HttpURLConnection.HTTP_NOT_MODIFIED) { - response.body?.closeQuietly() + response.closeQuietly() throw HttpException(response) } return response @@ -116,8 +121,12 @@ class FaviconFetcher( return ImageSource(data, fileSystem, diskCacheKey, this) } - private fun ResponseBody.toImageSource(): ImageSource { - return ImageSource(source(), options.context, FaviconMetadata(mangaSource)) + private fun ResponseBody.toImageSource(response: Closeable): ImageSource { + return ImageSource( + source().withExtraCloseable(response).buffer(), + options.context, + FaviconMetadata(mangaSource), + ) } private fun Response.toDataSource(): DataSource { diff --git a/app/src/main/java/org/koitharu/kotatsu/details/domain/BranchComparator.kt b/app/src/main/java/org/koitharu/kotatsu/details/domain/BranchComparator.kt index d0b5a23c3..4d93a464c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/domain/BranchComparator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/domain/BranchComparator.kt @@ -1,6 +1,8 @@ package org.koitharu.kotatsu.details.domain -class BranchComparator : Comparator { +import org.koitharu.kotatsu.details.ui.model.MangaBranch - override fun compare(o1: String?, o2: String?): Int = compareValues(o1, o2) -} \ No newline at end of file +class BranchComparator : Comparator { + + override fun compare(o1: MangaBranch, o2: MangaBranch): Int = compareValues(o1.name, o2.name) +} 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 f3d357c7b..d5ac27063 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 @@ -1,12 +1,12 @@ package org.koitharu.kotatsu.details.ui import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.os.Bundle import android.transition.Slide import android.transition.TransitionManager import android.view.Gravity -import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup @@ -19,31 +19,31 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.lifecycle.Observer -import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.ui.BaseActivity +import org.koitharu.kotatsu.base.ui.dialog.RecyclerViewAlertDialog +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.os.ShortcutsUpdater import org.koitharu.kotatsu.databinding.ActivityDetailsBinding import org.koitharu.kotatsu.details.service.MangaPrefetchService +import org.koitharu.kotatsu.details.ui.adapter.branchAD import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.HistoryInfo +import org.koitharu.kotatsu.details.ui.model.MangaBranch import org.koitharu.kotatsu.download.ui.worker.DownloadStartedObserver import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.reader.ui.ReaderState import org.koitharu.kotatsu.utils.ViewBadge -import org.koitharu.kotatsu.utils.ext.getDisplayMessage -import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat import org.koitharu.kotatsu.utils.ext.textAndVisible import javax.inject.Inject @@ -53,7 +53,9 @@ class DetailsActivity : BaseActivity(), View.OnClickListener, BottomSheetHeaderBar.OnExpansionChangeListener, - NoModalBottomSheetOwner, View.OnLongClickListener, PopupMenu.OnMenuItemClickListener { + NoModalBottomSheetOwner, + View.OnLongClickListener, + PopupMenu.OnMenuItemClickListener { override val bsHeader: BottomSheetHeaderBar? get() = binding.headerChapters @@ -254,18 +256,19 @@ class DetailsActivity : } private fun showBranchPopupMenu() { - val menu = PopupMenu(this, binding.headerChapters ?: binding.buttonDropdown) - val currentBranch = viewModel.selectedBranchValue - for (branch in viewModel.branches.value ?: return) { - val item = menu.menu.add(R.id.group_branches, Menu.NONE, Menu.NONE, branch) - item.isChecked = branch == currentBranch + var dialog: DialogInterface? = null + val listener = OnListItemClickListener { item, _ -> + viewModel.setSelectedBranch(item.name) + dialog?.dismiss() } - menu.menu.setGroupCheckable(R.id.group_branches, true, true) - menu.setOnMenuItemClickListener { item -> - viewModel.setSelectedBranch(item.title?.toString()) - true - } - menu.show() + dialog = RecyclerViewAlertDialog.Builder(this) + .addAdapterDelegate(branchAD(listener)) + .setCancelable(true) + .setNegativeButton(android.R.string.cancel, null) + .setTitle(R.string.translations) + .setItems(viewModel.branches.value.orEmpty()) + .create() + .also { it.show() } } private fun openReader(isIncognitoMode: Boolean) { diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt index 5aab7e32a..73ff6a880 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsMenuProvider.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.launch import org.koitharu.kotatsu.R import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.core.os.ShortcutsUpdater +import org.koitharu.kotatsu.details.ui.model.MangaBranch import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource @@ -124,18 +125,20 @@ class DetailsMenuProvider( return true } - private fun showSaveConfirmation(manga: Manga, chaptersCount: Int, branches: List) { + private fun showSaveConfirmation(manga: Manga, chaptersCount: Int, branches: List) { val dialogBuilder = MaterialAlertDialogBuilder(activity) .setTitle(R.string.save_manga) .setNegativeButton(android.R.string.cancel, null) if (branches.size > 1) { - val items = Array(branches.size) { i -> branches[i].orEmpty() } - val currentBranch = viewModel.selectedBranchIndex.value ?: -1 + val items = Array(branches.size) { i -> branches[i].name.orEmpty() } + val currentBranch = branches.indexOfFirst { it.isSelected } val checkedIndices = BooleanArray(branches.size) { i -> i == currentBranch } dialogBuilder.setMultiChoiceItems(items, checkedIndices) { _, i, checked -> checkedIndices[i] = checked }.setPositiveButton(R.string.save) { _, _ -> - val selectedBranches = branches.filterIndexedTo(HashSet()) { i, _ -> checkedIndices[i] } + val selectedBranches = branches.mapIndexedNotNullTo(HashSet()) { i, b -> + if (checkedIndices[i]) b.name else null + } val chaptersIds = manga.chapters?.mapNotNullToSet { c -> if (c.branch in selectedBranches) c.id else null } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 1c5804997..ecb76143b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -36,6 +36,7 @@ import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.details.domain.BranchComparator import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.HistoryInfo +import org.koitharu.kotatsu.details.ui.model.MangaBranch import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.history.domain.HistoryRepository @@ -44,7 +45,6 @@ import org.koitharu.kotatsu.local.data.LocalStorageChanges import org.koitharu.kotatsu.local.domain.LocalMangaRepository import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource -import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus @@ -158,17 +158,15 @@ class DetailsViewModel @Inject constructor( scrobblingInfo.filterNotNull() }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) - val branches: LiveData> = delegate.manga.map { - val chapters = it?.chapters ?: return@map emptyList() - chapters.mapToSet { x -> x.branch }.sortedWith(BranchComparator()) - }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) - - val selectedBranchIndex = combine( - branches.asFlow(), + val branches: LiveData> = combine( + delegate.manga, delegate.selectedBranch, - ) { branches, selected -> - branches.indexOf(selected) - }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, -1) + ) { m, b -> + val chapters = m?.chapters ?: return@combine emptyList() + chapters.groupBy { x -> x.branch } + .map { x -> MangaBranch(x.key, x.value.size, x.key == b) } + .sortedWith(BranchComparator()) + }.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList()) val selectedBranchName = delegate.selectedBranch .asFlowLiveData(viewModelScope.coroutineContext, null) diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchAD.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchAD.kt new file mode 100644 index 000000000..5e11ad8d2 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchAD.kt @@ -0,0 +1,39 @@ +package org.koitharu.kotatsu.details.ui.adapter + +import android.graphics.Color +import android.text.Spannable +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import androidx.core.text.buildSpannedString +import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.databinding.ItemCheckableNewBinding +import org.koitharu.kotatsu.details.ui.model.MangaBranch +import org.koitharu.kotatsu.utils.ext.getThemeColor + +fun branchAD( + clickListener: OnListItemClickListener, +) = adapterDelegateViewBinding( + { inflater, parent -> ItemCheckableNewBinding.inflate(inflater, parent, false) }, +) { + + val clickAdapter = AdapterDelegateClickListenerAdapter(this, clickListener) + itemView.setOnClickListener(clickAdapter) + val counterColorSpan = ForegroundColorSpan(context.getThemeColor(android.R.attr.textColorSecondary, Color.LTGRAY)) + val counterSizeSpan = RelativeSizeSpan(0.86f) + + bind { + binding.root.text = buildSpannedString { + append(item.name ?: getString(R.string.system_default)) + append(' ') + append(' ') + val start = length + append(item.count.toString()) + setSpan(counterColorSpan, start, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + setSpan(counterSizeSpan, start, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + } + binding.root.isChecked = item.isSelected + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt index 2ca602df9..cc3f73f73 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/adapter/BranchesAdapter.kt @@ -1,45 +1,16 @@ package org.koitharu.kotatsu.details.ui.adapter -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.BaseAdapter -import android.widget.TextView -import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.parsers.util.replaceWith +import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter +import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener +import org.koitharu.kotatsu.details.ui.model.MangaBranch -class BranchesAdapter : BaseAdapter() { +class BranchesAdapter( + list: List, + listener: OnListItemClickListener, +) : ListDelegationAdapter>() { - private val dataSet = ArrayList() - - override fun getCount(): Int { - return dataSet.size + init { + delegatesManager.addDelegate(branchAD(listener)) + items = list } - - override fun getItem(position: Int): Any? { - return dataSet[position] - } - - override fun getItemId(position: Int): Long { - return dataSet[position].hashCode().toLong() - } - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = convertView ?: LayoutInflater.from(parent.context) - .inflate(R.layout.item_branch, parent, false) - (view as TextView).text = dataSet[position] - return view - } - - override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = convertView ?: LayoutInflater.from(parent.context) - .inflate(R.layout.item_branch_dropdown, parent, false) - (view as TextView).text = dataSet[position] - return view - } - - fun setItems(items: Collection) { - dataSet.replaceWith(items) - notifyDataSetChanged() - } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt new file mode 100644 index 000000000..8588187f6 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/model/MangaBranch.kt @@ -0,0 +1,32 @@ +package org.koitharu.kotatsu.details.ui.model + +import org.koitharu.kotatsu.list.ui.model.ListModel + +class MangaBranch( + val name: String?, + val count: Int, + val isSelected: Boolean, +) : ListModel { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MangaBranch + + if (name != other.name) return false + if (count != other.count) return false + return isSelected == other.isSelected + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + count + result = 31 * result + isSelected.hashCode() + return result + } + + override fun toString(): String { + return "$name: $count" + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt b/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt index 4e5d31b4b..dc2af369a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/ui/ImportWorker.kt @@ -9,7 +9,6 @@ import android.net.Uri import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.PendingIntentCompat -import androidx.core.content.ContextCompat import androidx.hilt.work.HiltWorker import androidx.work.Constraints import androidx.work.CoroutineWorker @@ -71,6 +70,7 @@ class ImportWorker @AssistedInject constructor( .setPriority(NotificationCompat.PRIORITY_MIN) .setDefaults(0) .setSilent(true) + .setOngoing(true) .setProgress(0, 0, true) .setSmallIcon(android.R.drawable.stat_sys_download) .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE) 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 270c0684b..e9a314b48 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 @@ -50,6 +50,7 @@ import org.koitharu.kotatsu.utils.GridTouchHelper import org.koitharu.kotatsu.utils.IdlingDetector import org.koitharu.kotatsu.utils.ShareHelper import org.koitharu.kotatsu.utils.ext.hasGlobalPoint +import org.koitharu.kotatsu.utils.ext.isRtl import org.koitharu.kotatsu.utils.ext.observeWithPrevious import org.koitharu.kotatsu.utils.ext.postDelayed import org.koitharu.kotatsu.utils.ext.setValueRounded @@ -156,6 +157,7 @@ class ReaderActivity : if (binding.appbarTop.isVisible) { lifecycle.postDelayed(hideUiRunnable, TimeUnit.SECONDS.toMillis(1)) } + binding.slider.isRtl = mode == ReaderMode.REVERSED } override fun onCreateOptionsMenu(menu: Menu): Boolean { diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderManager.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderManager.kt index 5bf7eee5b..e454a7106 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderManager.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderManager.kt @@ -4,7 +4,7 @@ import androidx.annotation.IdRes import androidx.fragment.app.FragmentManager import androidx.fragment.app.commit import org.koitharu.kotatsu.core.prefs.ReaderMode -import org.koitharu.kotatsu.reader.ui.pager.BaseReader +import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment import org.koitharu.kotatsu.reader.ui.pager.reversed.ReversedReaderFragment import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment import org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonReaderFragment @@ -15,7 +15,7 @@ class ReaderManager( @IdRes private val containerResId: Int, ) { - private val modeMap = EnumMap>>(ReaderMode::class.java) + private val modeMap = EnumMap>>(ReaderMode::class.java) init { modeMap[ReaderMode.STANDARD] = PagerReaderFragment::class.java @@ -23,8 +23,8 @@ class ReaderManager( modeMap[ReaderMode.WEBTOON] = WebtoonReaderFragment::class.java } - val currentReader: BaseReader<*>? - get() = fragmentManager.findFragmentById(containerResId) as? BaseReader<*> + val currentReader: BaseReaderFragment<*>? + get() = fragmentManager.findFragmentById(containerResId) as? BaseReaderFragment<*> val currentMode: ReaderMode? get() { @@ -40,7 +40,7 @@ class ReaderManager( } } - fun replace(reader: BaseReader<*>) { + fun replace(reader: BaseReaderFragment<*>) { fragmentManager.commit { setReorderingAllowed(true) replace(containerResId, reader) diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt index 139902b4f..51c55af04 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/config/ReaderConfigBottomSheet.kt @@ -11,7 +11,6 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.FragmentManager import androidx.fragment.app.activityViewModels -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import com.google.android.material.button.MaterialButtonToggleGroup import com.google.android.material.slider.Slider @@ -148,8 +147,8 @@ class ReaderConfigBottomSheet : } } - override fun onActivityResult(uri: Uri?) { - viewModel.onActivityResult(uri) + override fun onActivityResult(result: Uri?) { + viewModel.onActivityResult(result) dismissAllowingStateLoss() } @@ -157,7 +156,6 @@ class ReaderConfigBottomSheet : val helper = ScreenOrientationHelper(requireActivity()) orientationHelper = helper helper.observeAutoOrientation() - .flowWithLifecycle(lifecycle) .onEach { binding.buttonScreenRotate.isGone = it }.launchIn(viewLifecycleScope) diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReaderFragment.kt similarity index 95% rename from app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReader.kt rename to app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReaderFragment.kt index eee0769bd..954c866c8 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReaderFragment.kt @@ -12,7 +12,7 @@ import org.koitharu.kotatsu.utils.ext.getParcelableCompat private const val KEY_STATE = "state" -abstract class BaseReader : BaseFragment() { +abstract class BaseReaderFragment : BaseFragment() { protected val viewModel by activityViewModels() private var stateToSave: ReaderState? = null diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt index 1447a4b36..5bf30b8a5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/reversed/ReversedReaderFragment.kt @@ -13,8 +13,8 @@ import org.koitharu.kotatsu.core.os.NetworkState import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.ui.ReaderState -import org.koitharu.kotatsu.reader.ui.pager.BaseReader import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment import org.koitharu.kotatsu.utils.ext.doOnPageChanged @@ -26,7 +26,7 @@ import javax.inject.Inject import kotlin.math.absoluteValue @AndroidEntryPoint -class ReversedReaderFragment : BaseReader() { +class ReversedReaderFragment : BaseReaderFragment() { @Inject lateinit var networkState: NetworkState @@ -70,6 +70,7 @@ class ReversedReaderFragment : BaseReader() { override fun onDestroyView() { pagerAdapter = null + binding.pager.adapter = null super.onDestroyView() } diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt index 49b4e1239..75462cfb0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/standard/PagerReaderFragment.kt @@ -13,8 +13,8 @@ import org.koitharu.kotatsu.core.os.NetworkState import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.ui.ReaderState -import org.koitharu.kotatsu.reader.ui.pager.BaseReader import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.utils.ext.doOnPageChanged import org.koitharu.kotatsu.utils.ext.isAnimationsEnabled @@ -25,7 +25,7 @@ import javax.inject.Inject import kotlin.math.absoluteValue @AndroidEntryPoint -class PagerReaderFragment : BaseReader() { +class PagerReaderFragment : BaseReaderFragment() { @Inject lateinit var networkState: NetworkState @@ -69,6 +69,7 @@ class PagerReaderFragment : BaseReader() { override fun onDestroyView() { pagesAdapter = null + binding.pager.adapter = null super.onDestroyView() } diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt index 226961f32..e836c7fd0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonReaderFragment.kt @@ -12,8 +12,8 @@ import org.koitharu.kotatsu.core.os.NetworkState import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding import org.koitharu.kotatsu.reader.domain.PageLoader import org.koitharu.kotatsu.reader.ui.ReaderState -import org.koitharu.kotatsu.reader.ui.pager.BaseReader import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter +import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.utils.ext.findCenterViewPosition import org.koitharu.kotatsu.utils.ext.firstVisibleItemPosition @@ -22,7 +22,7 @@ import org.koitharu.kotatsu.utils.ext.viewLifecycleScope import javax.inject.Inject @AndroidEntryPoint -class WebtoonReaderFragment : BaseReader() { +class WebtoonReaderFragment : BaseReaderFragment() { @Inject lateinit var networkState: NetworkState @@ -60,6 +60,7 @@ class WebtoonReaderFragment : BaseReader() { override fun onDestroyView() { webtoonAdapter = null + binding.recyclerView.adapter = null super.onDestroyView() } diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt index 77d1c8bd1..23f1f2766 100644 --- a/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/ui/SuggestionsWorker.kt @@ -28,6 +28,7 @@ import coil.ImageLoader import coil.request.ImageRequest import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.take @@ -100,6 +101,7 @@ class SuggestionsWorker @AssistedInject constructor( .setPriority(NotificationCompat.PRIORITY_MIN) .setCategory(NotificationCompat.CATEGORY_SERVICE) .setDefaults(0) + .setOngoing(true) .setSilent(true) .setProgress(0, 0, true) .setSmallIcon(android.R.drawable.stat_notify_sync) @@ -145,15 +147,27 @@ class SuggestionsWorker @AssistedInject constructor( .take(MAX_RESULTS) suggestionRepository.replace(suggestions) if (appSettings.isSuggestionsNotificationAvailable) { - runCatchingCancellable { - val manga = suggestions[Random.nextInt(0, suggestions.size / 3)] - val details = mangaRepositoryFactory.create(manga.manga.source) - .getDetails(manga.manga) - if (details !in tagsBlacklist) { + for (i in 0..3) { + try { + val manga = suggestions[Random.nextInt(0, suggestions.size / 3)] + val details = mangaRepositoryFactory.create(manga.manga.source) + .getDetails(manga.manga) + if (details.rating > 0 && details.rating < RATING_MIN) { + continue + } + if (details.isNsfw && appSettings.isSuggestionsExcludeNsfw) { + continue + } + if (details in tagsBlacklist) { + continue + } showNotification(details) + break + } catch (e: CancellationException) { + throw e + } catch (e: Exception) { + e.printStackTraceDebug() } - }.onFailure { - it.printStackTraceDebug() } } return suggestions.size @@ -178,7 +192,8 @@ class SuggestionsWorker @AssistedInject constructor( if (blacklist.isNotEmpty()) { list.removeAll { manga -> manga in blacklist } } - list + list.shuffle() + list.take(MAX_SOURCE_RESULTS) }.onFailure { it.printStackTraceDebug() }.getOrDefault(emptyList()) @@ -296,8 +311,10 @@ class SuggestionsWorker @AssistedInject constructor( private const val MANGA_CHANNEL_ID = "suggestions" private const val WORKER_NOTIFICATION_ID = 36 private const val MAX_RESULTS = 80 + private const val MAX_SOURCE_RESULTS = 14 private const val MAX_RAW_RESULTS = 200 private const val TAG_EQ_THRESHOLD = 0.4f + private const val RATING_MIN = 0.5f private val preferredSortOrders = listOf( SortOrder.UPDATED, diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt index 58bfdfb07..fc32bf3ee 100644 --- a/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt +++ b/app/src/main/java/org/koitharu/kotatsu/tracker/work/TrackWorker.kt @@ -219,6 +219,7 @@ class TrackWorker @AssistedInject constructor( .setPriority(NotificationCompat.PRIORITY_MIN) .setCategory(NotificationCompat.CATEGORY_SERVICE) .setDefaults(0) + .setOngoing(true) .setSilent(true) .setProgress(0, 0, true) .setSmallIcon(android.R.drawable.stat_notify_sync) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/PreferenceIconTarget.kt b/app/src/main/java/org/koitharu/kotatsu/utils/PreferenceIconTarget.kt deleted file mode 100644 index edece17d7..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/utils/PreferenceIconTarget.kt +++ /dev/null @@ -1,22 +0,0 @@ -package org.koitharu.kotatsu.utils - -import android.graphics.drawable.Drawable -import androidx.preference.Preference -import coil.target.Target - -class PreferenceIconTarget( - private val preference: Preference, -) : Target { - - override fun onError(error: Drawable?) { - preference.icon = error - } - - override fun onStart(placeholder: Drawable?) { - preference.icon = placeholder - } - - override fun onSuccess(result: Drawable) { - preference.icon = result - } -} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt index dec45bde0..1b696422e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/FragmentExt.kt @@ -34,18 +34,21 @@ fun Fragment.addMenuProvider(provider: MenuProvider) { } @MainThread -suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner = suspendCancellableCoroutine { cont -> +suspend fun Fragment.awaitViewLifecycle(): LifecycleOwner { val liveData = viewLifecycleOwnerLiveData - val observer = object : Observer { - override fun onChanged(value: LifecycleOwner?) { - if (value != null) { - liveData.removeObserver(this) - cont.resume(value) + liveData.value?.let { return it } + return suspendCancellableCoroutine { cont -> + val observer = object : Observer { + override fun onChanged(value: LifecycleOwner?) { + if (value != null) { + liveData.removeObserver(this) + cont.resume(value) + } } } - } - liveData.observeForever(observer) - cont.invokeOnCancellation { - liveData.removeObserver(observer) + liveData.observeForever(observer) + cont.invokeOnCancellation { + liveData.removeObserver(observer) + } } } 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 index 0f0ac0180..af2ddca22 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt @@ -4,9 +4,9 @@ import android.view.View import androidx.core.graphics.Insets fun Insets.end(view: View): Int { - return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) left else right + return if (view.isRtl) left else right } fun Insets.start(view: View): Int { - return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) right else left -} \ No newline at end of file + return if (view.isRtl) right else left +} diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt index e1f235652..7a6d95240 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt @@ -54,7 +54,9 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) { is IOException -> getDisplayMessage(message, resources) ?: localizedMessage else -> localizedMessage -} ?: resources.getString(R.string.error_occurred) +}.ifNullOrEmpty { + resources.getString(R.string.error_occurred) +} private fun getDisplayMessage(msg: String?, resources: Resources): String? = when { msg.isNullOrEmpty() -> null diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt index fb2f70804..8d08cabd9 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt @@ -200,3 +200,9 @@ fun V.setChecked(checked: Boolean, animate: Boolean) where V : View, V : Che jumpDrawablesToCurrentState() } } + +var View.isRtl: Boolean + get() = layoutDirection == View.LAYOUT_DIRECTION_RTL + set(value) { + layoutDirection = if (value) View.LAYOUT_DIRECTION_RTL else View.LAYOUT_DIRECTION_LTR + } diff --git a/app/src/main/res/layout/item_branch.xml b/app/src/main/res/layout/item_branch.xml deleted file mode 100644 index 1dd867cee..000000000 --- a/app/src/main/res/layout/item_branch.xml +++ /dev/null @@ -1,9 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_branch_dropdown.xml b/app/src/main/res/layout/item_branch_dropdown.xml deleted file mode 100644 index 0881d3e3d..000000000 --- a/app/src/main/res/layout/item_branch_dropdown.xml +++ /dev/null @@ -1,11 +0,0 @@ - - \ 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 6b5c9f939..d3af2b733 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -414,4 +414,5 @@ Downloads have been removed Downloads have been cancelled Do you want to receive personalized manga suggestions? + Translations diff --git a/build.gradle b/build.gradle index daf977e07..c420893c0 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.0.1' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21' - classpath 'com.google.dagger:hilt-android-gradle-plugin:2.45' + classpath 'com.google.dagger:hilt-android-gradle-plugin:2.46.1' } }