Improve branch selection
This commit is contained in:
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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<T>(context: Context) {
|
||||
|
||||
private val recyclerView = RecyclerView(context)
|
||||
private val delegatesManager = AdapterDelegatesManager<List<T>>()
|
||||
private var items: List<T>? = 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<T> {
|
||||
delegate.setTitle(titleResId)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setTitle(title: CharSequence): Builder<T> {
|
||||
delegate.setTitle(title)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setIcon(@DrawableRes iconId: Int): Builder<T> {
|
||||
delegate.setIcon(iconId)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPositiveButton(
|
||||
@StringRes textId: Int,
|
||||
listener: DialogInterface.OnClickListener,
|
||||
): Builder<T> {
|
||||
delegate.setPositiveButton(textId, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setNegativeButton(
|
||||
@StringRes textId: Int,
|
||||
listener: DialogInterface.OnClickListener? = null
|
||||
): Builder<T> {
|
||||
delegate.setNegativeButton(textId, listener)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setCancelable(isCancelable: Boolean): Builder<T> {
|
||||
delegate.setCancelable(isCancelable)
|
||||
return this
|
||||
}
|
||||
|
||||
fun addAdapterDelegate(subject: AdapterDelegate<List<T>>): Builder<T> {
|
||||
delegatesManager.addDelegate(subject)
|
||||
return this
|
||||
}
|
||||
|
||||
fun setItems(list: List<T>): Builder<T> {
|
||||
items = list
|
||||
return this
|
||||
}
|
||||
|
||||
fun create(): RecyclerViewAlertDialog {
|
||||
recyclerView.adapter = ListDelegationAdapter(delegatesManager).also {
|
||||
it.items = items
|
||||
}
|
||||
return RecyclerViewAlertDialog(delegate.create())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package org.koitharu.kotatsu.base.ui.list
|
||||
|
||||
import android.view.View
|
||||
|
||||
interface OnListItemClickListener<I> {
|
||||
fun interface OnListItemClickListener<I> {
|
||||
|
||||
fun onItemClick(item: I, view: View)
|
||||
|
||||
fun onItemLongClick(item: I, view: View) = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Manga>.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<String?, List<MangaChapter>>(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
|
||||
}
|
||||
|
||||
@@ -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<Any>, 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 {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.koitharu.kotatsu.details.domain
|
||||
|
||||
class BranchComparator : Comparator<String?> {
|
||||
import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
||||
|
||||
override fun compare(o1: String?, o2: String?): Int = compareValues(o1, o2)
|
||||
}
|
||||
class BranchComparator : Comparator<MangaBranch> {
|
||||
|
||||
override fun compare(o1: MangaBranch, o2: MangaBranch): Int = compareValues(o1.name, o2.name)
|
||||
}
|
||||
|
||||
@@ -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<ActivityDetailsBinding>(),
|
||||
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<MangaBranch> { 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<MangaBranch>(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) {
|
||||
|
||||
@@ -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<String?>) {
|
||||
private fun showSaveConfirmation(manga: Manga, chaptersCount: Int, branches: List<MangaBranch>) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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<List<String?>> = 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<List<MangaBranch>> = 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)
|
||||
|
||||
@@ -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<MangaBranch>,
|
||||
) = adapterDelegateViewBinding<MangaBranch, MangaBranch, ItemCheckableNewBinding>(
|
||||
{ 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
|
||||
}
|
||||
}
|
||||
@@ -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<MangaBranch>,
|
||||
listener: OnListItemClickListener<MangaBranch>,
|
||||
) : ListDelegationAdapter<List<MangaBranch>>() {
|
||||
|
||||
private val dataSet = ArrayList<String?>()
|
||||
|
||||
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<String?>) {
|
||||
dataSet.replaceWith(items)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<out BaseReader<*>>>(ReaderMode::class.java)
|
||||
private val modeMap = EnumMap<ReaderMode, Class<out BaseReaderFragment<*>>>(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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.koitharu.kotatsu.utils.ext.getParcelableCompat
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
||||
abstract class BaseReader<B : ViewBinding> : BaseFragment<B>() {
|
||||
abstract class BaseReaderFragment<B : ViewBinding> : BaseFragment<B>() {
|
||||
|
||||
protected val viewModel by activityViewModels<ReaderViewModel>()
|
||||
private var stateToSave: ReaderState? = null
|
||||
@@ -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<FragmentReaderStandardBinding>() {
|
||||
class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var networkState: NetworkState
|
||||
@@ -70,6 +70,7 @@ class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
|
||||
override fun onDestroyView() {
|
||||
pagerAdapter = null
|
||||
binding.pager.adapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<FragmentReaderStandardBinding>() {
|
||||
class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var networkState: NetworkState
|
||||
@@ -69,6 +69,7 @@ class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
|
||||
override fun onDestroyView() {
|
||||
pagesAdapter = null
|
||||
binding.pager.adapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
||||
@@ -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<FragmentReaderWebtoonBinding>() {
|
||||
class WebtoonReaderFragment : BaseReaderFragment<FragmentReaderWebtoonBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var networkState: NetworkState
|
||||
@@ -60,6 +60,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
|
||||
override fun onDestroyView() {
|
||||
webtoonAdapter = null
|
||||
binding.recyclerView.adapter = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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<LifecycleOwner?> {
|
||||
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<LifecycleOwner?> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
return if (view.isRtl) right else left
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -200,3 +200,9 @@ fun <V> 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
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
style="?android:attr/spinnerItemStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeightSmall"
|
||||
android:gravity="center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge" />
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<CheckedTextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="?android:attr/spinnerDropDownItemStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/listPreferredItemHeightSmall"
|
||||
android:drawableEnd="?android:listChoiceIndicatorSingle"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
tools:text="Scanlator" />
|
||||
@@ -414,4 +414,5 @@
|
||||
<string name="downloads_removed">Downloads have been removed</string>
|
||||
<string name="downloads_cancelled">Downloads have been cancelled</string>
|
||||
<string name="suggestions_enable_prompt">Do you want to receive personalized manga suggestions?</string>
|
||||
<string name="translations">Translations</string>
|
||||
</resources>
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user