Compare commits
20 Commits
v3.3-beta1
...
v3.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc2820ec11 | ||
|
|
312fb033e0 | ||
|
|
18bc4dc739 | ||
|
|
2b61b27271 | ||
|
|
58c9f75b91 | ||
|
|
790f1fb8a3 | ||
|
|
5c4f3f7fe4 | ||
|
|
86ead09080 | ||
|
|
0932507346 | ||
|
|
21f7b7120a | ||
|
|
473135bfc5 | ||
|
|
ce7960e5e9 | ||
|
|
17c440ee43 | ||
|
|
5d881ca154 | ||
|
|
e4b29b3ff9 | ||
|
|
046aaa0649 | ||
|
|
f653c74ce8 | ||
|
|
0c73c55b9d | ||
|
|
8dec54e96f | ||
|
|
859ae966c8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
/.idea/dictionaries
|
||||
/.idea/modules.xml
|
||||
/.idea/misc.xml
|
||||
/.idea/discord.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
|
||||
@@ -14,8 +14,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 32
|
||||
versionCode 408
|
||||
versionName '3.3-beta1'
|
||||
versionCode 409
|
||||
versionName '3.3'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -66,7 +66,7 @@ android {
|
||||
}
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation('com.github.nv95:kotatsu-parsers:05a93e2380') {
|
||||
implementation('com.github.nv95:kotatsu-parsers:f46c5add46') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ dependencies {
|
||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
|
||||
|
||||
implementation 'io.insert-koin:koin-android:3.2.0'
|
||||
implementation 'io.coil-kt:coil-base:2.0.0'
|
||||
implementation 'io.coil-kt:coil-base:2.1.0'
|
||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0'
|
||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||
|
||||
|
||||
@@ -88,6 +88,9 @@
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity"
|
||||
android:label="@string/search" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity"
|
||||
android:label="@string/search" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.main.ui.protect.ProtectActivity"
|
||||
android:noHistory="true"
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.fragment.app.strictmode.FragmentStrictMode
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.bookmarks.bookmarksModule
|
||||
import org.koitharu.kotatsu.core.db.databaseModule
|
||||
import org.koitharu.kotatsu.core.github.githubModule
|
||||
@@ -43,6 +44,7 @@ class KotatsuApp : Application() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(AppCrashHandler(applicationContext))
|
||||
AppCompatDelegate.setDefaultNightMode(get<AppSettings>().theme)
|
||||
registerActivityLifecycleCallbacks(get<AppProtectHelper>())
|
||||
registerActivityLifecycleCallbacks(get<ActivityRecreationHandle>())
|
||||
val widgetUpdater = WidgetUpdater(applicationContext)
|
||||
widgetUpdater.subscribeToFavourites(get())
|
||||
widgetUpdater.subscribeToHistory(get())
|
||||
|
||||
@@ -43,9 +43,13 @@ abstract class BaseActivity<B : ViewBinding> :
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
val settings = get<AppSettings>()
|
||||
val isAmoled = settings.isAmoledTheme
|
||||
val isDynamic = settings.isDynamicTheme
|
||||
// TODO support DialogWhenLarge theme
|
||||
when {
|
||||
settings.isAmoledTheme -> setTheme(R.style.ThemeOverlay_Kotatsu_AMOLED)
|
||||
settings.isDynamicTheme -> setTheme(R.style.Theme_Kotatsu_Monet)
|
||||
isAmoled && isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet_Amoled)
|
||||
isAmoled -> setTheme(R.style.Theme_Kotatsu_Amoled)
|
||||
isDynamic -> setTheme(R.style.Theme_Kotatsu_Monet)
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
@@ -2,11 +2,11 @@ package org.koitharu.kotatsu.base.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import androidx.appcompat.app.AppCompatDialog
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
@@ -14,6 +14,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.dialog.AppBottomSheetDialog
|
||||
import org.koitharu.kotatsu.utils.ext.displayCompat
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
@@ -33,6 +34,20 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
): View {
|
||||
val binding = onInflateView(inflater, container)
|
||||
viewBinding = binding
|
||||
|
||||
// Enforce max width for tablets
|
||||
val width = resources.getDimensionPixelSize(R.dimen.bottom_sheet_width)
|
||||
if (width > 0) {
|
||||
behavior?.maxWidth = width
|
||||
}
|
||||
|
||||
// Set peek height to 50% display height
|
||||
requireContext().displayCompat?.let {
|
||||
val metrics = DisplayMetrics()
|
||||
it.getRealMetrics(metrics)
|
||||
behavior?.peekHeight = metrics.heightPixels / 2
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@@ -42,11 +57,7 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return if (resources.getBoolean(R.bool.is_tablet)) {
|
||||
AppCompatDialog(context, R.style.Theme_Kotatsu_Dialog)
|
||||
} else {
|
||||
AppBottomSheetDialog(requireContext(), theme)
|
||||
}
|
||||
return AppBottomSheetDialog(requireContext(), theme)
|
||||
}
|
||||
|
||||
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B
|
||||
|
||||
@@ -21,7 +21,7 @@ class AppBottomSheetDialog(context: Context, theme: Int) : BottomSheetDialog(con
|
||||
if (drawEdgeToEdge) {
|
||||
// Copied from super.onAttachedToWindow:
|
||||
val edgeToEdgeFlags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
// Fix super-class's window flag bug by respecting the intial system UI visibility:
|
||||
// Fix super-class's window flag bug by respecting the initial system UI visibility:
|
||||
window.decorView.systemUiVisibility = edgeToEdgeFlags or initialSystemUiVisibility
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.koitharu.kotatsu.base.ui.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application.ActivityLifecycleCallbacks
|
||||
import android.os.Bundle
|
||||
import java.util.*
|
||||
|
||||
class ActivityRecreationHandle : ActivityLifecycleCallbacks {
|
||||
|
||||
private val activities = WeakHashMap<Activity, Unit>()
|
||||
|
||||
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||
activities[activity] = Unit
|
||||
}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) = Unit
|
||||
|
||||
override fun onActivityResumed(activity: Activity) = Unit
|
||||
|
||||
override fun onActivityPaused(activity: Activity) = Unit
|
||||
|
||||
override fun onActivityStopped(activity: Activity) = Unit
|
||||
|
||||
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) = Unit
|
||||
|
||||
override fun onActivityDestroyed(activity: Activity) {
|
||||
activities.remove(activity)
|
||||
}
|
||||
|
||||
fun recreateAll() {
|
||||
val snapshot = activities.keys.toList()
|
||||
snapshot.forEach { it.recreate() }
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ 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
|
||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||
|
||||
// Entity to model
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.collection.arraySetOf
|
||||
@@ -52,7 +51,7 @@ class AppSettings(context: Context) {
|
||||
get() = prefs.getString(KEY_THEME, null)?.toIntOrNull() ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
|
||||
val isDynamicTheme: Boolean
|
||||
get() = prefs.getBoolean(KEY_DYNAMIC_THEME, false)
|
||||
get() = DynamicColors.isDynamicColorAvailable() && prefs.getBoolean(KEY_DYNAMIC_THEME, false)
|
||||
|
||||
val isAmoledTheme: Boolean
|
||||
get() = prefs.getBoolean(KEY_THEME_AMOLED, false)
|
||||
@@ -152,7 +151,7 @@ class AppSettings(context: Context) {
|
||||
}
|
||||
|
||||
fun markKnownSources(sources: Collection<MangaSource>) {
|
||||
sourcesOrder = sourcesOrder + sources.map { it.name }
|
||||
sourcesOrder = (sourcesOrder + sources.map { it.name }).distinct()
|
||||
}
|
||||
|
||||
val isPagesNumbersEnabled: Boolean
|
||||
@@ -324,12 +323,5 @@ class AppSettings(context: Context) {
|
||||
private const val NETWORK_NEVER = 0
|
||||
private const val NETWORK_ALWAYS = 1
|
||||
private const val NETWORK_NON_METERED = 2
|
||||
|
||||
val isDynamicColorAvailable: Boolean
|
||||
get() = DynamicColors.isDynamicColorAvailable() ||
|
||||
(isSamsung && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
|
||||
private val isSamsung
|
||||
get() = Build.MANUFACTURER.equals("samsung", ignoreCase = true)
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
|
||||
@@ -208,7 +208,7 @@ class DetailsActivity :
|
||||
}
|
||||
R.id.action_related -> {
|
||||
viewModel.manga.value?.let {
|
||||
startActivity(GlobalSearchActivity.newIntent(this, it.title))
|
||||
startActivity(MultiSearchActivity.newIntent(this, it.title))
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ 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.ArrayAdapter
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@@ -84,9 +86,9 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
|
||||
right = insets.right,
|
||||
bottom = insets.bottom,
|
||||
)
|
||||
binding.toolbar.updatePadding(
|
||||
top = insets.top,
|
||||
)
|
||||
binding.toolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil.request.Disposable
|
||||
@@ -13,6 +14,7 @@ import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.search.ui.multi.adapter.ItemSizeResolver
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
@@ -21,6 +23,7 @@ fun mangaGridItemAD(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
clickListener: OnListItemClickListener<Manga>,
|
||||
sizeResolver: ItemSizeResolver?,
|
||||
) = adapterDelegateViewBinding<MangaGridModel, ListModel, ItemMangaGridBinding>(
|
||||
{ inflater, parent -> ItemMangaGridBinding.inflate(inflater, parent, false) }
|
||||
) {
|
||||
@@ -34,6 +37,11 @@ fun mangaGridItemAD(
|
||||
itemView.setOnLongClickListener {
|
||||
clickListener.onItemLongClick(item.manga, it)
|
||||
}
|
||||
if (sizeResolver != null) {
|
||||
itemView.updateLayoutParams {
|
||||
width = sizeResolver.cellWidth
|
||||
}
|
||||
}
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.title
|
||||
|
||||
@@ -18,7 +18,7 @@ class MangaListAdapter(
|
||||
delegatesManager
|
||||
.addDelegate(ITEM_TYPE_MANGA_LIST, mangaListItemAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(ITEM_TYPE_MANGA_LIST_DETAILED, mangaListDetailedItemAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener))
|
||||
.addDelegate(ITEM_TYPE_MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, listener, null))
|
||||
.addDelegate(ITEM_TYPE_LOADING_FOOTER, loadingFooterAD())
|
||||
.addDelegate(ITEM_TYPE_LOADING_STATE, loadingStateAD())
|
||||
.addDelegate(ITEM_TYPE_DATE, relatedDateItemAD())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.list.ui.filter
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import java.util.*
|
||||
import androidx.lifecycle.LiveData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -13,6 +13,8 @@ import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
|
||||
class FilterCoordinator(
|
||||
private val repository: RemoteMangaRepository,
|
||||
@@ -27,7 +29,7 @@ class FilterCoordinator(
|
||||
}
|
||||
private var availableTagsDeferred = loadTagsAsync()
|
||||
|
||||
val items = getItemsFlow()
|
||||
val items: LiveData<List<FilterItem>> = getItemsFlow()
|
||||
.asLiveDataDistinct(coroutineScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
init {
|
||||
@@ -104,7 +106,7 @@ class FilterCoordinator(
|
||||
query: String,
|
||||
): List<FilterItem> {
|
||||
val sortOrders = repository.sortOrders.sortedBy { it.ordinal }
|
||||
val tags = mergeTags(state.tags, allTags.tags).sortedBy { it.title }
|
||||
val tags = mergeTags(state.tags, allTags.tags).toList()
|
||||
val list = ArrayList<FilterItem>(tags.size + sortOrders.size + 3)
|
||||
if (query.isEmpty()) {
|
||||
if (sortOrders.isNotEmpty()) {
|
||||
@@ -158,7 +160,7 @@ class FilterCoordinator(
|
||||
}
|
||||
|
||||
private fun mergeTags(primary: Set<MangaTag>, secondary: Set<MangaTag>): Set<MangaTag> {
|
||||
val result = TreeSet(TagTitleComparator())
|
||||
val result = TreeSet(TagTitleComparator(repository.source.locale))
|
||||
result.addAll(secondary)
|
||||
result.addAll(primary)
|
||||
return result
|
||||
@@ -191,11 +193,14 @@ class FilterCoordinator(
|
||||
}
|
||||
}
|
||||
|
||||
private class TagTitleComparator : Comparator<MangaTag> {
|
||||
private class TagTitleComparator(lc: String?) : Comparator<MangaTag> {
|
||||
|
||||
override fun compare(o1: MangaTag, o2: MangaTag) = compareValues(
|
||||
o1.title.lowercase(),
|
||||
o2.title.lowercase(),
|
||||
)
|
||||
private val collator = lc?.let { Collator.getInstance(Locale(it)) }
|
||||
|
||||
override fun compare(o1: MangaTag, o2: MangaTag): Int {
|
||||
val t1 = o1.title.lowercase()
|
||||
val t2 = o2.title.lowercase()
|
||||
return collator?.compare(t1, t2) ?: compareValues(t1, t2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ fun Manga.toGridModel(counter: Int) = MangaGridModel(
|
||||
suspend fun List<Manga>.toUi(
|
||||
mode: ListMode,
|
||||
countersProvider: CountersProvider,
|
||||
): List<ListModel> = when (mode) {
|
||||
): List<MangaItemModel> = when (mode) {
|
||||
ListMode.LIST -> map { it.toListModel(countersProvider.getCounter(it.id)) }
|
||||
ListMode.DETAILED_LIST -> map { it.toListDetailedModel(countersProvider.getCounter(it.id)) }
|
||||
ListMode.GRID -> map { it.toGridModel(countersProvider.getCounter(it.id)) }
|
||||
@@ -58,7 +58,7 @@ suspend fun <C : MutableCollection<ListModel>> List<Manga>.toUi(
|
||||
|
||||
fun List<Manga>.toUi(
|
||||
mode: ListMode,
|
||||
): List<ListModel> = when (mode) {
|
||||
): List<MangaItemModel> = when (mode) {
|
||||
ListMode.LIST -> map { it.toListModel(0) }
|
||||
ListMode.DETAILED_LIST -> map { it.toListDetailedModel(0) }
|
||||
ListMode.GRID -> map { it.toGridModel(0) }
|
||||
|
||||
@@ -2,13 +2,13 @@ package org.koitharu.kotatsu.local.data
|
||||
|
||||
import android.content.Context
|
||||
import com.tomclaw.cache.DiskLruCache
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.koitharu.kotatsu.parsers.util.longHashCode
|
||||
import org.koitharu.kotatsu.utils.FileSize
|
||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||
import org.koitharu.kotatsu.utils.ext.subdir
|
||||
import org.koitharu.kotatsu.utils.ext.takeIfReadable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
class PagesCache(context: Context) {
|
||||
|
||||
|
||||
@@ -15,11 +15,11 @@ import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||
import org.koitharu.kotatsu.local.data.MangaIndex
|
||||
import org.koitharu.kotatsu.local.data.TempFileFilter
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.longHashCode
|
||||
import org.koitharu.kotatsu.parsers.util.toCamelCase
|
||||
import org.koitharu.kotatsu.utils.AlphanumComparator
|
||||
import org.koitharu.kotatsu.utils.CompositeMutex
|
||||
import org.koitharu.kotatsu.utils.ext.deleteAwait
|
||||
import org.koitharu.kotatsu.utils.ext.longHashCode
|
||||
import org.koitharu.kotatsu.utils.ext.readText
|
||||
import org.koitharu.kotatsu.utils.ext.resolveName
|
||||
import java.io.File
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.main
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.core.os.ShortcutsRepository
|
||||
import org.koitharu.kotatsu.main.ui.MainViewModel
|
||||
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
||||
@@ -11,6 +12,7 @@ import org.koitharu.kotatsu.main.ui.protect.ProtectViewModel
|
||||
val mainModule
|
||||
get() = module {
|
||||
single { AppProtectHelper(get()) }
|
||||
single { ActivityRecreationHandle() }
|
||||
factory { ShortcutsRepository(androidContext(), get(), get(), get()) }
|
||||
viewModel { MainViewModel(get(), get()) }
|
||||
viewModel { ProtectViewModel(get(), get()) }
|
||||
|
||||
@@ -45,7 +45,7 @@ import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
|
||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||
import org.koitharu.kotatsu.search.ui.SearchActivity
|
||||
import org.koitharu.kotatsu.search.ui.global.GlobalSearchActivity
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||
@@ -233,6 +233,8 @@ class MainActivity :
|
||||
}
|
||||
binding.toolbarCard.updateLayoutParams<MarginLayoutParams> {
|
||||
topMargin = insets.top + bottomMargin
|
||||
leftMargin = insets.left
|
||||
rightMargin = insets.right
|
||||
}
|
||||
binding.root.updatePadding(
|
||||
left = insets.left,
|
||||
@@ -268,7 +270,7 @@ class MainActivity :
|
||||
if (source != null) {
|
||||
startActivity(SearchActivity.newIntent(this, source, query))
|
||||
} else {
|
||||
startActivity(GlobalSearchActivity.newIntent(this, query))
|
||||
startActivity(MultiSearchActivity.newIntent(this, query))
|
||||
}
|
||||
searchSuggestionViewModel.saveQuery(query)
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
|
||||
fun Manga.filterChapters(branch: String?): Manga {
|
||||
if (chapters.isNullOrEmpty()) return this
|
||||
return copy(chapters = chapters?.filter { it.branch == branch })
|
||||
return withChapters(chapters = chapters?.filter { it.branch == branch })
|
||||
}
|
||||
|
||||
private fun Manga.copy(chapters: List<MangaChapter>?) = Manga(
|
||||
private fun Manga.withChapters(chapters: List<MangaChapter>?) = Manga(
|
||||
id = id,
|
||||
title = title,
|
||||
altTitle = altTitle,
|
||||
|
||||
@@ -7,7 +7,7 @@ import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
|
||||
import org.koitharu.kotatsu.search.ui.SearchViewModel
|
||||
import org.koitharu.kotatsu.search.ui.global.GlobalSearchViewModel
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchViewModel
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||
|
||||
val searchModule
|
||||
@@ -16,11 +16,7 @@ val searchModule
|
||||
factory { MangaSearchRepository(get(), get(), androidContext(), get()) }
|
||||
factory { MangaSuggestionsProvider.createSuggestions(androidContext()) }
|
||||
|
||||
viewModel { params ->
|
||||
SearchViewModel(MangaRepository(params[0]), params[1], get())
|
||||
}
|
||||
viewModel { query ->
|
||||
GlobalSearchViewModel(query.get(), get(), get())
|
||||
}
|
||||
viewModel { params -> SearchViewModel(MangaRepository(params[0]), params[1], get()) }
|
||||
viewModel { SearchSuggestionViewModel(get(), get()) }
|
||||
viewModel { params -> MultiSearchViewModel(params[0], get()) }
|
||||
}
|
||||
@@ -14,16 +14,16 @@ import org.koin.core.parameter.parametersOf
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaTags
|
||||
import org.koitharu.kotatsu.databinding.ActivitySearchGlobalBinding
|
||||
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
|
||||
|
||||
class MangaListActivity : BaseActivity<ActivitySearchGlobalBinding>() {
|
||||
class MangaListActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivitySearchGlobalBinding.inflate(layoutInflater))
|
||||
setContentView(ActivityContainerBinding.inflate(layoutInflater))
|
||||
val tags = intent.getParcelableExtra<ParcelableMangaTags>(EXTRA_TAGS)?.tags ?: run {
|
||||
finishAfterTransition()
|
||||
return
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
package org.koitharu.kotatsu.search.ui.global
|
||||
|
||||
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 org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivitySearchGlobalBinding
|
||||
|
||||
class GlobalSearchActivity : BaseActivity<ActivitySearchGlobalBinding>() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivitySearchGlobalBinding.inflate(layoutInflater))
|
||||
val query = intent.getStringExtra(EXTRA_QUERY)
|
||||
|
||||
if (query == null) {
|
||||
finishAfterTransition()
|
||||
return
|
||||
}
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
title = query
|
||||
supportActionBar?.subtitle = getString(R.string.search_results)
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, GlobalSearchFragment.newInstance(query))
|
||||
.commit()
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
with(binding.toolbar) {
|
||||
updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right
|
||||
)
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_QUERY = "query"
|
||||
|
||||
fun newIntent(context: Context, query: String) =
|
||||
Intent(context, GlobalSearchActivity::class.java)
|
||||
.putExtra(EXTRA_QUERY, query)
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.koitharu.kotatsu.search.ui.global
|
||||
|
||||
import android.view.Menu
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import org.koin.core.parameter.parametersOf
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.list.ui.MangaListFragment
|
||||
import org.koitharu.kotatsu.utils.ext.stringArgument
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
class GlobalSearchFragment : MangaListFragment() {
|
||||
|
||||
override val viewModel by viewModel<GlobalSearchViewModel> {
|
||||
parametersOf(query)
|
||||
}
|
||||
|
||||
private val query by stringArgument(ARG_QUERY)
|
||||
|
||||
override fun onScrolledToEnd() = Unit
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_remote, menu)
|
||||
return super.onCreateActionMode(mode, menu)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val ARG_QUERY = "query"
|
||||
|
||||
fun newInstance(query: String) = GlobalSearchFragment().withArgs(1) {
|
||||
putString(ARG_QUERY, query)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package org.koitharu.kotatsu.search.ui.global
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.list.ui.MangaListViewModel
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.search.domain.MangaSearchRepository
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.onFirst
|
||||
|
||||
class GlobalSearchViewModel(
|
||||
private val query: String,
|
||||
private val repository: MangaSearchRepository,
|
||||
settings: AppSettings
|
||||
) : MangaListViewModel(settings) {
|
||||
|
||||
private val mangaList = MutableStateFlow<List<Manga>?>(null)
|
||||
private val hasNextPage = MutableStateFlow(false)
|
||||
private val listError = MutableStateFlow<Throwable?>(null)
|
||||
private var searchJob: Job? = null
|
||||
|
||||
override val content = combine(
|
||||
mangaList,
|
||||
createListModeFlow(),
|
||||
listError,
|
||||
hasNextPage
|
||||
) { list, mode, error, hasNext ->
|
||||
when {
|
||||
list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))
|
||||
list == null -> listOf(LoadingState)
|
||||
list.isEmpty() -> listOf(
|
||||
EmptyState(
|
||||
icon = R.drawable.ic_book_search,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_search_holder_secondary,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
)
|
||||
else -> {
|
||||
val result = ArrayList<ListModel>(list.size + 1)
|
||||
list.toUi(result, mode)
|
||||
when {
|
||||
error != null -> result += error.toErrorFooter()
|
||||
hasNext -> result += LoadingFooter
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
init {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
override fun onRetry() {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
searchJob?.cancel()
|
||||
searchJob = repository.globalSearch(query)
|
||||
.catch { e ->
|
||||
listError.value = e
|
||||
loadingCounter.reset()
|
||||
}.onStart {
|
||||
mangaList.value = null
|
||||
listError.value = null
|
||||
loadingCounter.increment()
|
||||
hasNextPage.value = true
|
||||
}.onEmpty {
|
||||
mangaList.value = emptyList()
|
||||
}.onCompletion {
|
||||
loadingCounter.reset()
|
||||
hasNextPage.value = false
|
||||
}.onFirst {
|
||||
loadingCounter.reset()
|
||||
}.onEach {
|
||||
mangaList.value = mangaList.value?.plus(it) ?: listOf(it)
|
||||
}.launchIn(viewModelScope + Dispatchers.Default)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package org.koitharu.kotatsu.search.ui.multi
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.databinding.ActivitySearchMultiBinding
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
|
||||
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.search.ui.SearchActivity
|
||||
import org.koitharu.kotatsu.search.ui.multi.adapter.ItemSizeResolver
|
||||
import org.koitharu.kotatsu.search.ui.multi.adapter.MultiSearchAdapter
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.findViewsByType
|
||||
|
||||
class MultiSearchActivity : BaseActivity<ActivitySearchMultiBinding>(), MangaListListener, ActionMode.Callback {
|
||||
|
||||
private val viewModel by viewModel<MultiSearchViewModel> {
|
||||
parametersOf(intent.getStringExtra(EXTRA_QUERY).orEmpty())
|
||||
}
|
||||
private lateinit var adapter: MultiSearchAdapter
|
||||
private lateinit var selectionDecoration: MangaSelectionDecoration
|
||||
private var actionMode: ActionMode? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivitySearchMultiBinding.inflate(layoutInflater))
|
||||
|
||||
val itemCLickListener = object : OnListItemClickListener<MultiSearchListModel> {
|
||||
override fun onItemClick(item: MultiSearchListModel, view: View) {
|
||||
startActivity(SearchActivity.newIntent(view.context, item.source, viewModel.query.value))
|
||||
}
|
||||
}
|
||||
val sizeResolver = ItemSizeResolver(resources, get())
|
||||
selectionDecoration = MangaSelectionDecoration(this)
|
||||
adapter = MultiSearchAdapter(
|
||||
lifecycleOwner = this,
|
||||
coil = get(),
|
||||
listener = this,
|
||||
itemClickListener = itemCLickListener,
|
||||
sizeResolver = sizeResolver,
|
||||
selectionDecoration = selectionDecoration,
|
||||
)
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setSubtitle(R.string.search_results)
|
||||
}
|
||||
|
||||
viewModel.query.observe(this) { title = it }
|
||||
viewModel.list.observe(this) { adapter.items = it }
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
with(binding.toolbar) {
|
||||
updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
topMargin = insets.top
|
||||
}
|
||||
}
|
||||
binding.recyclerView.updatePadding(
|
||||
bottom = insets.bottom,
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onItemClick(item: Manga, view: View) {
|
||||
if (selectionDecoration.checkedItemsCount != 0) {
|
||||
selectionDecoration.toggleItemChecked(item.id)
|
||||
if (selectionDecoration.checkedItemsCount == 0) {
|
||||
actionMode?.finish()
|
||||
} else {
|
||||
actionMode?.invalidate()
|
||||
invalidateItemDecorations()
|
||||
}
|
||||
return
|
||||
}
|
||||
val intent = DetailsActivity.newIntent(this, item)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onItemLongClick(item: Manga, view: View): Boolean {
|
||||
if (actionMode == null) {
|
||||
actionMode = startSupportActionMode(this)
|
||||
}
|
||||
return actionMode?.also {
|
||||
selectionDecoration.setItemIsChecked(item.id, true)
|
||||
invalidateItemDecorations()
|
||||
it.invalidate()
|
||||
} != null
|
||||
}
|
||||
|
||||
override fun onRetryClick(error: Throwable) {
|
||||
viewModel.doSearch(viewModel.query.value.orEmpty())
|
||||
}
|
||||
|
||||
override fun onTagRemoveClick(tag: MangaTag) = Unit
|
||||
|
||||
override fun onFilterClick() = Unit
|
||||
|
||||
override fun onEmptyActionClick() = Unit
|
||||
|
||||
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.menuInflater.inflate(R.menu.mode_remote, menu)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean {
|
||||
mode.title = selectionDecoration.checkedItemsCount.toString()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_share -> {
|
||||
ShareHelper(this).shareMangaLinks(collectSelectedItems())
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_favourite -> {
|
||||
FavouriteCategoriesBottomSheet.show(supportFragmentManager, collectSelectedItems())
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
R.id.action_save -> {
|
||||
DownloadService.confirmAndStart(this, collectSelectedItems())
|
||||
mode.finish()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyActionMode(mode: ActionMode) {
|
||||
selectionDecoration.clearSelection()
|
||||
invalidateItemDecorations()
|
||||
actionMode = null
|
||||
}
|
||||
|
||||
private fun collectSelectedItems(): Set<Manga> {
|
||||
return viewModel.getItems(selectionDecoration.checkedItemsIds)
|
||||
}
|
||||
|
||||
private fun invalidateItemDecorations() {
|
||||
binding.recyclerView.findViewsByType(RecyclerView::class.java).forEach {
|
||||
it.invalidateItemDecorations()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_QUERY = "query"
|
||||
|
||||
fun newIntent(context: Context, query: String) =
|
||||
Intent(context, MultiSearchActivity::class.java)
|
||||
.putExtra(EXTRA_QUERY, query)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.koitharu.kotatsu.search.ui.multi
|
||||
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
|
||||
class MultiSearchListModel(
|
||||
val source: MangaSource,
|
||||
val list: List<MangaItemModel>,
|
||||
) : ListModel {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as MultiSearchListModel
|
||||
|
||||
if (source != other.source) return false
|
||||
if (list != other.list) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = source.hashCode()
|
||||
result = 31 * result + list.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.koitharu.kotatsu.search.ui.multi
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.update
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.list.ui.model.*
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
|
||||
private const val MAX_PARALLELISM = 4
|
||||
|
||||
class MultiSearchViewModel(
|
||||
initialQuery: String,
|
||||
private val settings: AppSettings,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private var searchJob: Job? = null
|
||||
private val listData = MutableStateFlow<List<MultiSearchListModel>>(emptyList())
|
||||
private val loadingData = MutableStateFlow(false)
|
||||
private var listError = MutableStateFlow<Throwable?>(null)
|
||||
|
||||
val query = MutableLiveData(initialQuery)
|
||||
val list: LiveData<List<ListModel>> = combine(
|
||||
listData,
|
||||
loadingData,
|
||||
listError,
|
||||
) { list, loading, error ->
|
||||
when {
|
||||
list.isEmpty() -> listOf(
|
||||
when {
|
||||
loading -> LoadingState
|
||||
error != null -> error.toErrorState(canRetry = true)
|
||||
else -> EmptyState(
|
||||
icon = R.drawable.ic_book_search,
|
||||
textPrimary = R.string.nothing_found,
|
||||
textSecondary = R.string.text_search_holder_secondary,
|
||||
actionStringRes = 0,
|
||||
)
|
||||
}
|
||||
)
|
||||
loading -> list + LoadingFooter
|
||||
else -> list
|
||||
}
|
||||
}.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
|
||||
|
||||
init {
|
||||
doSearch(initialQuery)
|
||||
}
|
||||
|
||||
fun getItems(ids: Set<Long>): Set<Manga> {
|
||||
val result = HashSet<Manga>(ids.size)
|
||||
listData.value.forEach { x ->
|
||||
for (item in x.list) {
|
||||
if (item.id in ids) {
|
||||
result.add(item.manga)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun doSearch(q: String) {
|
||||
val prevJob = searchJob
|
||||
searchJob = launchJob(Dispatchers.Default) {
|
||||
prevJob?.cancelAndJoin()
|
||||
try {
|
||||
listError.value = null
|
||||
listData.value = emptyList()
|
||||
loadingData.value = true
|
||||
query.postValue(q)
|
||||
val errors = searchImpl(q)
|
||||
listError.value = errors.firstOrNull()
|
||||
} catch (e: Throwable) {
|
||||
listError.value = e
|
||||
} finally {
|
||||
loadingData.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun searchImpl(q: String): List<Throwable> {
|
||||
val sources = settings.getMangaSources(includeHidden = false)
|
||||
val dispatcher = Dispatchers.Default.limitedParallelism(MAX_PARALLELISM)
|
||||
return coroutineScope {
|
||||
sources.map { source ->
|
||||
async(dispatcher) {
|
||||
runCatching {
|
||||
val list = MangaRepository(source).getList(offset = 0, query = q)
|
||||
// .sortedBy { x -> x.title.levenshteinDistance(q) }
|
||||
.toUi(ListMode.GRID)
|
||||
if (list.isNotEmpty()) {
|
||||
val item = MultiSearchListModel(source, list)
|
||||
listData.update { x -> x + item }
|
||||
}
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}.exceptionOrNull()
|
||||
}
|
||||
}
|
||||
}.awaitAll().filterNotNull()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.koitharu.kotatsu.search.ui.multi.adapter
|
||||
|
||||
import android.content.res.Resources
|
||||
import kotlin.math.roundToInt
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
|
||||
class ItemSizeResolver(resources: Resources, settings: AppSettings) {
|
||||
|
||||
private val scaleFactor = settings.gridSize / 100f
|
||||
private val gridWidth = resources.getDimension(R.dimen.preferred_grid_width)
|
||||
|
||||
val cellWidth: Int
|
||||
get() = (gridWidth * scaleFactor).roundToInt()
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package org.koitharu.kotatsu.search.ui.multi.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.list.ui.adapter.*
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchListModel
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
class MultiSearchAdapter(
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
coil: ImageLoader,
|
||||
listener: MangaListListener,
|
||||
itemClickListener: OnListItemClickListener<MultiSearchListModel>,
|
||||
sizeResolver: ItemSizeResolver,
|
||||
selectionDecoration: MangaSelectionDecoration,
|
||||
) : AsyncListDifferDelegationAdapter<ListModel>(DiffCallback()) {
|
||||
|
||||
init {
|
||||
val pool = RecycledViewPool()
|
||||
delegatesManager
|
||||
.addDelegate(
|
||||
searchResultsAD(
|
||||
sharedPool = pool,
|
||||
lifecycleOwner = lifecycleOwner,
|
||||
coil = coil,
|
||||
sizeResolver = sizeResolver,
|
||||
selectionDecoration = selectionDecoration,
|
||||
listener = listener,
|
||||
itemClickListener = itemClickListener,
|
||||
)
|
||||
)
|
||||
.addDelegate(loadingStateAD())
|
||||
.addDelegate(loadingFooterAD())
|
||||
.addDelegate(emptyStateListAD(listener))
|
||||
.addDelegate(errorStateListAD(listener))
|
||||
}
|
||||
|
||||
private class DiffCallback : DiffUtil.ItemCallback<ListModel>() {
|
||||
|
||||
override fun areItemsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
|
||||
return when {
|
||||
oldItem is MultiSearchListModel && newItem is MultiSearchListModel -> {
|
||||
oldItem.source == newItem.source
|
||||
}
|
||||
else -> oldItem.javaClass == newItem.javaClass
|
||||
}
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: ListModel, newItem: ListModel): Boolean {
|
||||
return Intrinsics.areEqual(oldItem, newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.koitharu.kotatsu.search.ui.multi.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.databinding.ItemListGroupBinding
|
||||
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
|
||||
import org.koitharu.kotatsu.list.ui.adapter.mangaGridItemAD
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.search.ui.multi.MultiSearchListModel
|
||||
|
||||
fun searchResultsAD(
|
||||
sharedPool: RecycledViewPool,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
coil: ImageLoader,
|
||||
sizeResolver: ItemSizeResolver,
|
||||
selectionDecoration: MangaSelectionDecoration,
|
||||
listener: OnListItemClickListener<Manga>,
|
||||
itemClickListener: OnListItemClickListener<MultiSearchListModel>,
|
||||
) = adapterDelegateViewBinding<MultiSearchListModel, ListModel, ItemListGroupBinding>(
|
||||
{ layoutInflater, parent -> ItemListGroupBinding.inflate(layoutInflater, parent, false) }
|
||||
) {
|
||||
|
||||
binding.recyclerView.setRecycledViewPool(sharedPool)
|
||||
val adapter = ListDelegationAdapter(
|
||||
mangaGridItemAD(coil, lifecycleOwner, listener, sizeResolver)
|
||||
)
|
||||
binding.recyclerView.addItemDecoration(selectionDecoration)
|
||||
binding.recyclerView.adapter = adapter
|
||||
val spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing)
|
||||
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
|
||||
val eventListener = AdapterDelegateClickListenerAdapter(this, itemClickListener)
|
||||
itemView.setOnClickListener(eventListener)
|
||||
|
||||
bind {
|
||||
binding.textViewTitle.text = item.source.title
|
||||
adapter.items = item.list
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,15 @@ import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.TwoStatePreference
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import org.koin.android.ext.android.get
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||
import org.koitharu.kotatsu.parsers.util.names
|
||||
@@ -36,7 +40,7 @@ class AppearanceSettingsFragment :
|
||||
entryValues = ListMode.values().names()
|
||||
setDefaultValueCompat(ListMode.GRID.name)
|
||||
}
|
||||
findPreference<Preference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible = AppSettings.isDynamicColorAvailable
|
||||
findPreference<Preference>(AppSettings.KEY_DYNAMIC_THEME)?.isVisible = DynamicColors.isDynamicColorAvailable()
|
||||
findPreference<ListPreference>(AppSettings.KEY_DATE_FORMAT)?.run {
|
||||
entryValues = resources.getStringArray(R.array.date_formats)
|
||||
val now = Date().time
|
||||
@@ -71,10 +75,10 @@ class AppearanceSettingsFragment :
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
}
|
||||
AppSettings.KEY_DYNAMIC_THEME -> {
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
postRestart()
|
||||
}
|
||||
AppSettings.KEY_THEME_AMOLED -> {
|
||||
findPreference<Preference>(key)?.setSummary(R.string.restart_required)
|
||||
postRestart()
|
||||
}
|
||||
AppSettings.KEY_APP_PASSWORD -> {
|
||||
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
|
||||
@@ -98,4 +102,10 @@ class AppearanceSettingsFragment :
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun postRestart() {
|
||||
view?.postDelayed(400) {
|
||||
get<ActivityRecreationHandle>().recreateAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ class SettingsHeadersFragment : PreferenceHeaderFragmentCompat(), SlidingPaneLay
|
||||
|
||||
fun setTitle(title: CharSequence?) {
|
||||
currentTitle = title
|
||||
if (slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen) {
|
||||
if (slidingPaneLayout.isOpen) {
|
||||
activity?.title = title
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,8 @@ import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigAdapter
|
||||
import org.koitharu.kotatsu.settings.sources.adapter.SourceConfigListener
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
|
||||
class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
class SourcesSettingsFragment :
|
||||
BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
SourceConfigListener,
|
||||
SearchView.OnQueryTextListener,
|
||||
MenuItem.OnActionExpandListener,
|
||||
@@ -53,7 +54,6 @@ class SourcesSettingsFragment : BaseFragment<FragmentSettingsSourcesBinding>(),
|
||||
val sourcesAdapter = SourceConfigAdapter(this, get(), viewLifecycleOwner)
|
||||
with(binding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
// addItemDecoration(SourceConfigItemDecoration(view.context))
|
||||
adapter = sourcesAdapter
|
||||
reorderHelper = ItemTouchHelper(SourcesReorderCallback()).also {
|
||||
it.attachToRecyclerView(this)
|
||||
|
||||
@@ -51,6 +51,9 @@ class SourcesSettingsViewModel(
|
||||
} else {
|
||||
settings.hiddenSources + source.name
|
||||
}
|
||||
if (isEnabled) {
|
||||
settings.markKnownSources(setOf(source))
|
||||
}
|
||||
buildList()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package org.koitharu.kotatsu.settings.sources.adapter
|
||||
|
||||
import android.content.Context
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import org.koitharu.kotatsu.base.ui.list.decor.AbstractDividerItemDecoration
|
||||
|
||||
class SourceConfigItemDecoration(context: Context) : AbstractDividerItemDecoration(context) {
|
||||
|
||||
override fun shouldDrawDivider(
|
||||
above: RecyclerView.ViewHolder,
|
||||
below: RecyclerView.ViewHolder,
|
||||
): Boolean {
|
||||
return above.itemViewType != 0 && below.itemViewType != 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.Display
|
||||
import android.view.WindowManager
|
||||
import androidx.core.content.getSystemService
|
||||
|
||||
val Context.displayCompat: Display?
|
||||
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
display
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
getSystemService<WindowManager>()?.defaultDisplay
|
||||
}
|
||||
@@ -2,4 +2,13 @@ package org.koitharu.kotatsu.utils.ext
|
||||
|
||||
inline fun String?.ifNullOrEmpty(defaultValue: () -> String): String {
|
||||
return if (this.isNullOrEmpty()) defaultValue() else this
|
||||
}
|
||||
|
||||
fun String.longHashCode(): Long {
|
||||
var h = 1125899906842597L
|
||||
val len: Int = this.length
|
||||
for (i in 0 until len) {
|
||||
h = 31 * h + this[i].code
|
||||
}
|
||||
return h
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.utils.ext
|
||||
import android.app.Activity
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
@@ -138,4 +139,19 @@ val RecyclerView.isScrolledToTop: Boolean
|
||||
}
|
||||
val holder = findViewHolderForAdapterPosition(0)
|
||||
return holder != null && holder.itemView.top >= 0
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : View> ViewGroup.findViewsByType(clazz: Class<T>): Sequence<T> {
|
||||
if (childCount == 0) {
|
||||
return emptySequence()
|
||||
}
|
||||
return sequence {
|
||||
for (view in children) {
|
||||
if (clazz.isInstance(view)) {
|
||||
yield(clazz.cast(view)!!)
|
||||
} else if (view is ViewGroup && view.childCount != 0) {
|
||||
yieldAll(view.findViewsByType(clazz))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/src/main/res/anim/bottom_sheet_slide_in.xml
Normal file
10
app/src/main/res/anim/bottom_sheet_slide_in.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in">
|
||||
|
||||
<translate
|
||||
android:fromYDelta="100%p"
|
||||
android:toYDelta="0" />
|
||||
|
||||
</set>
|
||||
10
app/src/main/res/anim/bottom_sheet_slide_out.xml
Normal file
10
app/src/main/res/anim/bottom_sheet_slide_out.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="300"
|
||||
android:interpolator="@android:interpolator/fast_out_slow_in">
|
||||
|
||||
<translate
|
||||
android:fromYDelta="0"
|
||||
android:toYDelta="100%p" />
|
||||
|
||||
</set>
|
||||
15
app/src/main/res/drawable/sheet_toolbar_background.xml
Normal file
15
app/src/main/res/drawable/sheet_toolbar_background.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:left="-2dp"
|
||||
android:right="-2dp"
|
||||
android:top="-2dp">
|
||||
<shape>
|
||||
<solid android:color="@android:color/transparent" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?android:attr/divider" />
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
@@ -11,6 +11,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true"
|
||||
app:indicatorSize="@dimen/list_footer_height_inner" />
|
||||
app:indicatorSize="24dp"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -10,7 +10,7 @@
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
|
||||
29
app/src/main/res/layout/activity_search_multi.xml
Normal file
29
app/src/main/res/layout/activity_search_multi.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_scrollFlags="scroll|enterAlways" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
32
app/src/main/res/layout/item_list_group.xml
Normal file
32
app/src/main/res/layout/item_list_group.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
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:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="@dimen/grid_spacing_outer">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/grid_spacing"
|
||||
android:gravity="center_vertical|start"
|
||||
android:padding="@dimen/grid_spacing"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader"
|
||||
tools:text="@tools:sample/lorem[2]" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="@dimen/grid_spacing"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -9,6 +9,7 @@
|
||||
android:layout_width="@dimen/list_footer_height_inner"
|
||||
android:layout_height="@dimen/list_footer_height_inner"
|
||||
android:layout_gravity="center"
|
||||
android:indeterminate="true" />
|
||||
android:indeterminate="true"
|
||||
android:padding="8dp"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -27,8 +27,9 @@
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elegantTextHeight="false"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:lines="2"
|
||||
android:padding="8dp"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@drawable/sheet_toolbar_background">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@drawable/sheet_toolbar_background">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@drawable/sheet_toolbar_background">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
|
||||
@@ -153,8 +153,6 @@
|
||||
<string name="update_check_failed">Памылка пры праверцы абнаўленняў</string>
|
||||
<string name="no_update_available">Няма даступных абнаўленняў</string>
|
||||
<string name="right_to_left">Справа налева</string>
|
||||
<string name="prefer_rtl_reader">Аддаваць перавагу рэжыму справа налева</string>
|
||||
<string name="prefer_rtl_reader_summary">Вы можаце наладзіць рэжым чытання для кожнай мангі асобна</string>
|
||||
<string name="create_category">Стварыць катэгорыю</string>
|
||||
<string name="report_github">Пачаць абмеркаванне праблемы на GitHub</string>
|
||||
<string name="scale_mode">Маштабаванне</string>
|
||||
@@ -164,7 +162,6 @@
|
||||
<string name="zoom_mode_keep_start">Зыходны памер</string>
|
||||
<string name="black_dark_theme">Чорная цёмная тэма</string>
|
||||
<string name="black_dark_theme_summary">Карысна для AMOLED экранаў</string>
|
||||
<string name="restart_required">Патрэбны перазапуск</string>
|
||||
<string name="backup_restore">Рэзервовае капіяванне і аднаўленне</string>
|
||||
<string name="create_backup">Стварыць рэзервовую копію</string>
|
||||
<string name="restore_backup">Аднавіць данныя</string>
|
||||
|
||||
@@ -85,10 +85,7 @@
|
||||
<string name="zoom_mode_fit_height">An Höhe anpassen</string>
|
||||
<string name="black_dark_theme_summary">Nützlich für AMOLED-Bildschirme</string>
|
||||
<string name="black_dark_theme">Schwarzer dunkler Modus</string>
|
||||
<string name="restart_required">Neustart erforderlich</string>
|
||||
<string name="prefer_rtl_reader_summary">Du kannst den Lesemodus für jeden Manga separat einrichten</string>
|
||||
<string name="right_to_left">Von rechts nach links</string>
|
||||
<string name="prefer_rtl_reader">Von rechts nach links-Lesemodus bevorzugen</string>
|
||||
<string name="create_category">Neue Kategorie</string>
|
||||
<string name="backup_restore">Sicherung und Wiederherstellung</string>
|
||||
<string name="data_restored">Daten wiederhergestellt</string>
|
||||
@@ -278,4 +275,23 @@
|
||||
<string name="chapters_will_removed_background">Die Kapitel werden im Hintergrund entfernt. Das kann einige Zeit dauern</string>
|
||||
<string name="hide">Ausblenden</string>
|
||||
<string name="new_sources_text">Neue Manga-Quellen sind verfügbar</string>
|
||||
<string name="check_new_chapters_title">Nach neuen Kapiteln suchen und darüber informieren</string>
|
||||
<string name="show_notification_new_chapters_on">Sie erhalten Benachrichtigungen über Aktualisierungen der Manga, die Sie lesen</string>
|
||||
<string name="notifications_enable">Benachrichtigungen einschalten</string>
|
||||
<string name="empty_favourite_categories">Keine bevorzugten Kategorien</string>
|
||||
<string name="name">Name</string>
|
||||
<string name="edit">Bearbeiten</string>
|
||||
<string name="show_notification_new_chapters_off">Sie werden keine Benachrichtigungen erhalten, aber neue Kapitel werden in den Listen hervorgehoben</string>
|
||||
<string name="edit_category">Kategorie bearbeiten</string>
|
||||
<string name="bookmark_add">Lesezeichen hinzufügen</string>
|
||||
<string name="bookmarks">Lesezeichen</string>
|
||||
<string name="bookmark_removed">Lesezeichen entfernt</string>
|
||||
<string name="removed_from_history">Aus dem Verlauf entfernt</string>
|
||||
<string name="bookmark_remove">Lesezeichen entfernen</string>
|
||||
<string name="bookmark_added">Lesezeichen hinzugefügt</string>
|
||||
<string name="undo">Rückgängig</string>
|
||||
<string name="dns_over_https">DNS über HTTPS</string>
|
||||
<string name="default_mode">Standard-Modus</string>
|
||||
<string name="detect_reader_mode">Automatische Erkennung des Lesegerätmodus</string>
|
||||
<string name="detect_reader_mode_summary">Automatisch erkennen, ob ein Manga ein Webtoon ist</string>
|
||||
</resources>
|
||||
@@ -153,8 +153,6 @@
|
||||
<string name="update_check_failed">Fallo en la comprobación de actualizaciones</string>
|
||||
<string name="no_update_available">No hay actualizaciones disponibles</string>
|
||||
<string name="right_to_left">Derecha a izquierda (←)</string>
|
||||
<string name="prefer_rtl_reader">Preferir lector de derecha a izquierda (←)</string>
|
||||
<string name="prefer_rtl_reader_summary">Puedes configurar el modo de lectura para cada manga por separado</string>
|
||||
<string name="create_category">Nueva categoría</string>
|
||||
<string name="report_github">Crear incidencia en GitHub</string>
|
||||
<string name="scale_mode">Modo de escala</string>
|
||||
@@ -164,7 +162,6 @@
|
||||
<string name="zoom_mode_keep_start">Mantener al iniciar</string>
|
||||
<string name="black_dark_theme">Tema oscuro auténtico</string>
|
||||
<string name="black_dark_theme_summary">Útil para pantallas AMOLED</string>
|
||||
<string name="restart_required">Se requiere reinicio</string>
|
||||
<string name="backup_restore">Respaldo y restauración</string>
|
||||
<string name="create_backup">Crear copia de seguridad de datos</string>
|
||||
<string name="restore_backup">Restaurar desde la copia de seguridad</string>
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
<string name="restore_backup">Palauta varmuuskopiosta</string>
|
||||
<string name="create_backup">Luo tietojen varmuuskopio</string>
|
||||
<string name="backup_restore">Varmuuskopiointi ja palautus</string>
|
||||
<string name="restart_required">Uudelleenkäynnistys vaaditaan</string>
|
||||
<string name="black_dark_theme_summary">Hyödyllinen AMOLED-näytöille</string>
|
||||
<string name="black_dark_theme">Musta tumma teema</string>
|
||||
<string name="zoom_mode_keep_start">Pidä alussa</string>
|
||||
@@ -63,8 +62,6 @@
|
||||
<string name="scale_mode">Skaalaustila</string>
|
||||
<string name="report_github">Luo ongelma GitHubissa</string>
|
||||
<string name="create_category">Uusi luokka</string>
|
||||
<string name="prefer_rtl_reader_summary">Voit määrittää lukutilan jokaiselle mangalle erikseen</string>
|
||||
<string name="prefer_rtl_reader">Mieluummin oikealta vasemmalle lukutila</string>
|
||||
<string name="right_to_left">Oikealta vasemmalle</string>
|
||||
<string name="no_update_available">Ei päivityksiä saatavilla</string>
|
||||
<string name="update_check_failed">Päivityksen tarkistus epäonnistui</string>
|
||||
@@ -278,4 +275,23 @@
|
||||
<string name="local_manga_processing">Tallennettujen mangojen käsittely</string>
|
||||
<string name="hide">Piilota</string>
|
||||
<string name="new_sources_text">Uusia mangalähteitä on saatavilla</string>
|
||||
<string name="check_new_chapters_title">Tarkista uudet luvut ja ilmoita siitä</string>
|
||||
<string name="notifications_enable">Ota ilmoitukset käyttöön</string>
|
||||
<string name="empty_favourite_categories">Ei suosikkiluokkia</string>
|
||||
<string name="name">Nimi</string>
|
||||
<string name="edit">Muokkaa</string>
|
||||
<string name="edit_category">Muokkaa luokkaa</string>
|
||||
<string name="show_notification_new_chapters_off">Et saa ilmoituksia, mutta uudet luvut korostetaan luetteloissa</string>
|
||||
<string name="show_notification_new_chapters_on">Saat ilmoituksia lukemasi mangan päivityksistä</string>
|
||||
<string name="bookmark_add">Lisää kirjanmerkki</string>
|
||||
<string name="bookmark_remove">Poista kirjanmerkki</string>
|
||||
<string name="bookmarks">Kirjanmerkit</string>
|
||||
<string name="bookmark_removed">Kirjanmerkki poistettu</string>
|
||||
<string name="bookmark_added">Kirjanmerkki lisätty</string>
|
||||
<string name="undo">Kumoa</string>
|
||||
<string name="removed_from_history">Poistettu historiasta</string>
|
||||
<string name="dns_over_https">DNS HTTPS:n kautta</string>
|
||||
<string name="detect_reader_mode">Lukijan automaattinen tunnistaminen</string>
|
||||
<string name="detect_reader_mode_summary">Tunnista automaattisesti, onko manga webtoon</string>
|
||||
<string name="default_mode">Oletustila</string>
|
||||
</resources>
|
||||
@@ -42,7 +42,6 @@
|
||||
<string name="restore_backup">Restaurer à partir d\'une sauvegarde</string>
|
||||
<string name="create_backup">Créer une sauvegarde des données</string>
|
||||
<string name="backup_restore">Sauvegarde et restauration</string>
|
||||
<string name="restart_required">Redémarrage nécessaire</string>
|
||||
<string name="black_dark_theme_summary">Utilise moins d\'énergie pour les écrans AMOLED</string>
|
||||
<string name="black_dark_theme">Noir</string>
|
||||
<string name="zoom_mode_keep_start">Garder au début</string>
|
||||
@@ -52,8 +51,6 @@
|
||||
<string name="scale_mode">Mode mise à l\'échelle</string>
|
||||
<string name="report_github">Signaler un problème sur GitHub</string>
|
||||
<string name="create_category">Nouvelle catégorie</string>
|
||||
<string name="prefer_rtl_reader_summary">Le mode de lecture peut être configuré séparément pour chaque série</string>
|
||||
<string name="prefer_rtl_reader">Préférer le lecteur de droite à gauche (←)</string>
|
||||
<string name="right_to_left">De droite à gauche (←)</string>
|
||||
<string name="no_update_available">Aucune mise à jour disponible</string>
|
||||
<string name="update_check_failed">Échec de la recherche de mise à jour</string>
|
||||
@@ -278,4 +275,23 @@
|
||||
<string name="local_manga_processing">Traitement des mangas sauvegardés</string>
|
||||
<string name="hide">Masquer</string>
|
||||
<string name="new_sources_text">De nouvelles sources de mangas sont disponibles</string>
|
||||
<string name="check_new_chapters_title">Vérifier les nouveaux chapitres et les notifier</string>
|
||||
<string name="notifications_enable">Activer les notifications</string>
|
||||
<string name="show_notification_new_chapters_on">Vous recevrez des notifications sur les mises à jour des mangas que vous lisez</string>
|
||||
<string name="show_notification_new_chapters_off">Vous ne recevrez pas de notifications mais les nouveaux chapitres seront mis en évidence dans les listes</string>
|
||||
<string name="empty_favourite_categories">Pas de catégories préférées</string>
|
||||
<string name="name">Nom</string>
|
||||
<string name="edit">Modifier</string>
|
||||
<string name="edit_category">Modifier la catégorie</string>
|
||||
<string name="bookmark_add">Ajouter un marque-page</string>
|
||||
<string name="bookmark_remove">Retirer le marque-page</string>
|
||||
<string name="bookmarks">Marque-pages</string>
|
||||
<string name="bookmark_added">Marque-page ajouté</string>
|
||||
<string name="bookmark_removed">Marque-page retiré</string>
|
||||
<string name="undo">Annuler</string>
|
||||
<string name="removed_from_history">Retiré de l\'historique</string>
|
||||
<string name="dns_over_https">DNS sur HTTPS</string>
|
||||
<string name="default_mode">Mode par défaut</string>
|
||||
<string name="detect_reader_mode">Mode de détection automatique du lecteur</string>
|
||||
<string name="detect_reader_mode_summary">Détecter automatiquement si un manga est un webtoon</string>
|
||||
</resources>
|
||||
@@ -91,13 +91,10 @@
|
||||
<string name="restore_backup">Ripristina da un backup</string>
|
||||
<string name="create_backup">Crea un backup dei dati</string>
|
||||
<string name="backup_restore">Backup e ripristino</string>
|
||||
<string name="restart_required">Riavvio richiesto</string>
|
||||
<string name="black_dark_theme_summary">Utile per gli schermi AMOLED</string>
|
||||
<string name="black_dark_theme">Tema nero scuro</string>
|
||||
<string name="report_github">Segnala un problema su GitHub</string>
|
||||
<string name="create_category">Nuova categoria</string>
|
||||
<string name="prefer_rtl_reader_summary">Puoi impostare la modalità di lettura per ogni manga separatamente</string>
|
||||
<string name="prefer_rtl_reader">Preferisci la lettura da destra a sinistra</string>
|
||||
<string name="right_to_left">Da destra a sinistra</string>
|
||||
<string name="no_update_available">Nessun aggiornamento disponibile</string>
|
||||
<string name="update_check_failed">Controllo dell\'aggiornamento fallito</string>
|
||||
@@ -278,4 +275,23 @@
|
||||
<string name="download_slowdown_summary">Aiuta ad evitare il blocco del tuo indirizzo IP</string>
|
||||
<string name="hide">Nascondi</string>
|
||||
<string name="new_sources_text">Sono disponibili nuove fonti di manga</string>
|
||||
<string name="show_notification_new_chapters_on">Riceverai notifiche sugli aggiornamenti del manga che stai leggendo</string>
|
||||
<string name="notifications_enable">Abilita le notifiche</string>
|
||||
<string name="show_notification_new_chapters_off">Non riceverai notifiche ma i nuovi capitoli saranno evidenziati nelle liste</string>
|
||||
<string name="empty_favourite_categories">Nessuna categoria preferita</string>
|
||||
<string name="name">Nome</string>
|
||||
<string name="edit">Modifica</string>
|
||||
<string name="edit_category">Modifica la categoria</string>
|
||||
<string name="check_new_chapters_title">Controlla i nuovi capitoli e notificarli</string>
|
||||
<string name="bookmarks">Segnalibri</string>
|
||||
<string name="removed_from_history">Rimosso dalla cronologia</string>
|
||||
<string name="bookmark_remove">Rimuovi il segnalibro</string>
|
||||
<string name="bookmark_add">Aggiungi un segnalibro</string>
|
||||
<string name="bookmark_removed">Segnalibro rimosso</string>
|
||||
<string name="bookmark_added">Segnalibro aggiunto</string>
|
||||
<string name="undo">Annulla</string>
|
||||
<string name="default_mode">Modalità predefinita</string>
|
||||
<string name="detect_reader_mode">Modalità di lettura a rilevamento automatico</string>
|
||||
<string name="dns_over_https">DNS su HTTPS</string>
|
||||
<string name="detect_reader_mode_summary">Rileva automaticamente se il manga è un webtoon</string>
|
||||
</resources>
|
||||
@@ -159,7 +159,6 @@
|
||||
<string name="update_check_failed">アップデートを確認する事が出来ませんでした</string>
|
||||
<string name="no_update_available">利用可能なアップデートはありません</string>
|
||||
<string name="right_to_left">右から左(←)</string>
|
||||
<string name="prefer_rtl_reader">右から左(←)の読書を好む</string>
|
||||
<string name="about_feedback">フィードバック</string>
|
||||
<string name="about_feedback_4pda">4PDAに関する話題</string>
|
||||
<string name="auth_complete">承認済み</string>
|
||||
@@ -222,8 +221,6 @@
|
||||
<string name="exclude_nsfw_from_history">NSFW漫画を履歴から除外する</string>
|
||||
<string name="queued">キュー</string>
|
||||
<string name="cookies_cleared">全てのCookieが削除されました</string>
|
||||
<string name="prefer_rtl_reader_summary">読み取りモードはシリーズごとに設定できます</string>
|
||||
<string name="restart_required">再起動が必要です</string>
|
||||
<string name="tracker_warning">一部のデバイスはシステムでの動作が異なり、バックグラウンドタスクが中断される可能性があります。</string>
|
||||
<string name="genres">ジャンル</string>
|
||||
<string name="scale_mode">スケールモード</string>
|
||||
@@ -278,4 +275,23 @@
|
||||
<string name="chapters_will_removed_background">チャプターはバックグラウンドで削除されます。時間がかかる場合があります</string>
|
||||
<string name="hide">隠す</string>
|
||||
<string name="new_sources_text">新しいマンガソースが利用可能になりました</string>
|
||||
<string name="check_new_chapters_title">新着チャプターの確認とお知らせ</string>
|
||||
<string name="show_notification_new_chapters_on">読んでいるマンガの更新情報をお知らせします</string>
|
||||
<string name="notifications_enable">通知を有効にする</string>
|
||||
<string name="show_notification_new_chapters_off">通知はありませんが、新しいチャプターはリストでハイライト表示されます</string>
|
||||
<string name="name">名称</string>
|
||||
<string name="edit">編集</string>
|
||||
<string name="edit_category">カテゴリーを編集する</string>
|
||||
<string name="empty_favourite_categories">お気に入りのカテゴリーはありません</string>
|
||||
<string name="bookmarks">ブックマーク</string>
|
||||
<string name="bookmark_removed">ブックマーク削除</string>
|
||||
<string name="undo">元に戻す</string>
|
||||
<string name="removed_from_history">履歴から削除</string>
|
||||
<string name="bookmark_add">ブックマークの追加</string>
|
||||
<string name="bookmark_remove">ブックマークの削除</string>
|
||||
<string name="bookmark_added">ブックマークを追加</string>
|
||||
<string name="dns_over_https">HTTPS 経由の DNS</string>
|
||||
<string name="detect_reader_mode">リーダーモードの自動検出</string>
|
||||
<string name="default_mode">デフォルトモード</string>
|
||||
<string name="detect_reader_mode_summary">マンガがウェブトゥーンかどうかを自動判定</string>
|
||||
</resources>
|
||||
@@ -26,9 +26,7 @@
|
||||
<string name="zoom_mode_fit_center">Tilpass sentrum</string>
|
||||
<string name="scale_mode">Skaleringsmodus</string>
|
||||
<string name="report_github">Opprett feilrapport på GitHub</string>
|
||||
<string name="prefer_rtl_reader_summary">Lesemodus kan settes opp for hver serie</string>
|
||||
<string name="right_to_left">Høyre-til-venstre (←)</string>
|
||||
<string name="prefer_rtl_reader">Foretrekk høyre-til-venstre (←)-leser</string>
|
||||
<string name="no_update_available">Ingen tilgjengelige oppdateringer</string>
|
||||
<string name="update_check_failed">Kunne ikke se etter oppdateringer</string>
|
||||
<string name="checking_for_updates">Ser etter oppdateringer …</string>
|
||||
@@ -104,7 +102,6 @@
|
||||
<string name="data_restored">Data gjenopprettet</string>
|
||||
<string name="restore_backup">Gjenopprett fra sikkerhetskopi</string>
|
||||
<string name="create_backup">Opprett sikkerhetskopi</string>
|
||||
<string name="restart_required">Omstart kreves</string>
|
||||
<string name="black_dark_theme_summary">Bruker mindre strøm på AMOLED-skjermer</string>
|
||||
<string name="black_dark_theme">Svart</string>
|
||||
<string name="create_category">Ny kategori</string>
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
<color name="onErrorContainer">#FFDAD4</color>
|
||||
<color name="scrollbar">#66FFFFFF</color>
|
||||
<color name="selector_foreground">#29FFFFFF</color>
|
||||
<color name="divider_default">#1FFFFFFF</color>
|
||||
|
||||
</resources>
|
||||
@@ -3,9 +3,12 @@
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu" parent="ThemeOverlay.Material3.Dark" />
|
||||
|
||||
<!--== AMOLED Mode Overlay ==-->
|
||||
<style name="ThemeOverlay.Kotatsu.AMOLED" parent="">
|
||||
<!-- Theme Colors -->
|
||||
<style name="Theme.Kotatsu.Amoled">
|
||||
<item name="colorSurface">@color/surface_amoled</item>
|
||||
<item name="android:colorBackground">@color/background_amoled</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Monet.Amoled">
|
||||
<item name="colorSurface">@color/surface_amoled</item>
|
||||
<item name="android:colorBackground">@color/background_amoled</item>
|
||||
</style>
|
||||
|
||||
@@ -140,8 +140,6 @@
|
||||
<string name="about">Sobre</string>
|
||||
<string name="checking_for_updates">Verificando atualizações…</string>
|
||||
<string name="update_check_failed">Não foi possível procurar atualizações</string>
|
||||
<string name="prefer_rtl_reader">Prefira o leitor da direita para a esquerda (←)</string>
|
||||
<string name="prefer_rtl_reader_summary">O modo de leitura pode ser configurado separadamente para cada série</string>
|
||||
<string name="backup_information">Você pode criar backup de seu histórico e favoritos e restaurá-lo</string>
|
||||
<string name="just_now">Agora mesmo</string>
|
||||
<string name="yesterday">Ontem</string>
|
||||
@@ -242,7 +240,6 @@
|
||||
<string name="create_backup">Criar backup de dados</string>
|
||||
<string name="text_local_holder_secondary">Salve-o de fontes online ou importe arquivos.</string>
|
||||
<string name="check_for_updates">Verifique se há atualizações</string>
|
||||
<string name="restart_required">É necessário reiniciar</string>
|
||||
<string name="text_feed_holder">Novos capítulos do que você está lendo são mostrados aqui</string>
|
||||
<string name="app_version">Versão %s</string>
|
||||
<string name="zoom_mode_fit_width">Ajustar à largura</string>
|
||||
@@ -278,4 +275,23 @@
|
||||
<string name="chapters_will_removed_background">Os capítulos serão removidos em segundo plano. Pode levar algum tempo</string>
|
||||
<string name="parallel_downloads">Downloads paralelos</string>
|
||||
<string name="new_sources_text">Novas fontes de mangá estão disponíveis</string>
|
||||
<string name="check_new_chapters_title">Verifique se há novos capítulos e notifique sobre isso</string>
|
||||
<string name="show_notification_new_chapters_on">Você receberá notificações sobre atualizações do mangá que está lendo</string>
|
||||
<string name="edit_category">Editar categoria</string>
|
||||
<string name="empty_favourite_categories">Nenhuma categoria favorita</string>
|
||||
<string name="bookmark_add">Adicionar marcador</string>
|
||||
<string name="bookmark_remove">Remover marcador</string>
|
||||
<string name="bookmarks">Marcadores</string>
|
||||
<string name="bookmark_added">Marcador adicionado</string>
|
||||
<string name="show_notification_new_chapters_off">Você não receberá notificações, mas novos capítulos serão destacados nas listas</string>
|
||||
<string name="notifications_enable">Ativar notificações</string>
|
||||
<string name="name">Nome</string>
|
||||
<string name="edit">Editar</string>
|
||||
<string name="bookmark_removed">Marcador removido</string>
|
||||
<string name="undo">Desfazer</string>
|
||||
<string name="removed_from_history">Removido do histórico</string>
|
||||
<string name="dns_over_https">DNS sobre HTTPS</string>
|
||||
<string name="default_mode">Modo padrão</string>
|
||||
<string name="detect_reader_mode">Detecção automática do modo de leitura</string>
|
||||
<string name="detect_reader_mode_summary">Detectar automaticamente se o mangá é webtoon</string>
|
||||
</resources>
|
||||
@@ -139,7 +139,6 @@
|
||||
<string name="scale_mode">Modo de escala</string>
|
||||
<string name="zoom_mode_fit_center">Centro de ajuste</string>
|
||||
<string name="zoom_mode_fit_width">Ajustar à largura</string>
|
||||
<string name="restart_required">É necessário reiniciar</string>
|
||||
<string name="backup_restore">Backup e restauração</string>
|
||||
<string name="create_backup">Criar backup de dados</string>
|
||||
<string name="restore_backup">Restaurar do backup</string>
|
||||
@@ -201,7 +200,6 @@
|
||||
<string name="recent_manga">Recente</string>
|
||||
<string name="other_storage">Outro armazenamento</string>
|
||||
<string name="text_search_holder_secondary">Tente reformular a consulta.</string>
|
||||
<string name="prefer_rtl_reader">Prefira o leitor da direita para a esquerda (←)</string>
|
||||
<string name="not_available">Não disponível</string>
|
||||
<string name="size_s">Tamanho: %s</string>
|
||||
<string name="text_history_holder_primary">O que você ler será exibido aqui</string>
|
||||
@@ -228,7 +226,6 @@
|
||||
<string name="text_clear_search_history_prompt">Remover todas as consultas de pesquisa recentes permanentemente\?</string>
|
||||
<string name="auth_required">Faça login para ver este conteúdo</string>
|
||||
<string name="text_categories_holder">Você pode usar categorias para organizar seus favoritos. Pressione «+» para criar uma categoria</string>
|
||||
<string name="prefer_rtl_reader_summary">O modo de leitura pode ser configurado separadamente para cada série</string>
|
||||
<string name="manga_save_location">Pasta para downloads</string>
|
||||
<string name="exclude_nsfw_from_history">Excluir mangá NSFW do histórico</string>
|
||||
<string name="date_format">Formato da data</string>
|
||||
|
||||
@@ -159,8 +159,6 @@
|
||||
<string name="update_check_failed">Не удалось проверить обновления</string>
|
||||
<string name="no_update_available">Нет доступных обновлений</string>
|
||||
<string name="right_to_left">Справа налево (←)</string>
|
||||
<string name="prefer_rtl_reader">Предпочитать читать справа налево (←)</string>
|
||||
<string name="prefer_rtl_reader_summary">Режим чтения может быть настроен отдельно для каждой серии</string>
|
||||
<string name="create_category">Создать категорию</string>
|
||||
<string name="scale_mode">Масштабирование</string>
|
||||
<string name="zoom_mode_fit_center">Вписать в экран</string>
|
||||
@@ -169,7 +167,6 @@
|
||||
<string name="zoom_mode_keep_start">Исходный размер</string>
|
||||
<string name="black_dark_theme">Чёрная</string>
|
||||
<string name="black_dark_theme_summary">Потребляет меньше энергии на экранах AMOLED</string>
|
||||
<string name="restart_required">Требуется перезапуск</string>
|
||||
<string name="backup_restore">Резервное копирование и восстановление</string>
|
||||
<string name="create_backup">Создать резервную копию</string>
|
||||
<string name="restore_backup">Восстановить данные</string>
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
<string name="sign_in">Logga in</string>
|
||||
<string name="auth_required">Logga in för att visa innehåll</string>
|
||||
<string name="default_s">Standard: %s</string>
|
||||
<string name="_and_x_more">...och %1$d till</string>
|
||||
<string name="_and_x_more">…och %1$d till</string>
|
||||
<string name="next">Nästa</string>
|
||||
<string name="password_length_hint">Lösenordet måste vara minst 4 tecken</string>
|
||||
<string name="search_only_on_s">Sök endast på %s</string>
|
||||
@@ -243,13 +243,10 @@
|
||||
<string name="zoom_mode_fit_center">Centrera</string>
|
||||
<string name="zoom_mode_fit_height">Anpassa mot höjd</string>
|
||||
<string name="zoom_mode_keep_start">Anpassa till start</string>
|
||||
<string name="prefer_rtl_reader">Föredra höger-till-vänster (←) läsare</string>
|
||||
<string name="preparing_">Förbereder…</string>
|
||||
<string name="prefer_rtl_reader_summary">Läsläge kan sättas up separat för varje serie</string>
|
||||
<string name="zoom_mode_fit_width">Anpassa mot bredd</string>
|
||||
<string name="black_dark_theme">Svart</string>
|
||||
<string name="black_dark_theme_summary">Använder mindre ström på AMOLED-skärmar</string>
|
||||
<string name="restart_required">Omstart krävs</string>
|
||||
<string name="backup_restore">Säkerhetskopiering och återställning</string>
|
||||
<string name="create_backup">Skapa säkerhetskopia</string>
|
||||
<string name="data_restored">Återställd</string>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
<string name="processing_">İşleniyor…</string>
|
||||
<string name="download_complete">İndirildi</string>
|
||||
<string name="downloads">İndirilenler</string>
|
||||
<string name="by_name">İsim</string>
|
||||
<string name="by_name">Ad</string>
|
||||
<string name="updated">Güncellenme</string>
|
||||
<string name="newest">Yeniler</string>
|
||||
<string name="by_rating">Puanlama</string>
|
||||
@@ -121,7 +121,6 @@
|
||||
<string name="clear_search_history">Arama geçmişini temizle</string>
|
||||
<string name="text_empty_holder_primary">Burası biraz boş…</string>
|
||||
<string name="rotate_screen">Ekranı döndür</string>
|
||||
<string name="prefer_rtl_reader_summary">Okuma modu her seri için ayrı bir şekilde ayarlanabilir</string>
|
||||
<string name="scale_mode">Ölçek modu</string>
|
||||
<string name="zoom_mode_fit_height">Yüksekliğe sığdır</string>
|
||||
<string name="black_dark_theme">Siyah</string>
|
||||
@@ -203,7 +202,6 @@
|
||||
<string name="long_ago">Uzun zaman önce</string>
|
||||
<string name="today">Bugün</string>
|
||||
<string name="no_update_available">Güncelleme yok</string>
|
||||
<string name="restart_required">Yeniden başlatma gerekli</string>
|
||||
<string name="chapters_checking_progress">Yeni bölümler denetleniyor: %1$d / %2$d</string>
|
||||
<string name="dynamic_theme">Dinamik tema</string>
|
||||
<string name="text_categories_holder">Favorilerinizi düzenlemek için kategorileri kullanabilirsiniz. Kategori oluşturmak için «+» düğmesine basın</string>
|
||||
@@ -278,4 +276,23 @@
|
||||
<string name="local_manga_processing">Kaydedilen manga işleme</string>
|
||||
<string name="hide">Gizle</string>
|
||||
<string name="new_sources_text">Yeni manga kaynakları var</string>
|
||||
<string name="show_notification_new_chapters_off">Bildirim almayacaksınız ancak yeni bölümler listelerde vurgulanacak</string>
|
||||
<string name="notifications_enable">Bildirimleri etkinleştir</string>
|
||||
<string name="check_new_chapters_title">Yeni bölümleri denetle ve bildirim gönder</string>
|
||||
<string name="show_notification_new_chapters_on">Okuduğunuz manga güncellemeleri hakkında bildirim alacaksınız</string>
|
||||
<string name="empty_favourite_categories">Favori kategori yok</string>
|
||||
<string name="name">Ad</string>
|
||||
<string name="edit">Düzenle</string>
|
||||
<string name="edit_category">Kategoriyi düzenle</string>
|
||||
<string name="bookmark_add">Yer imi ekle</string>
|
||||
<string name="bookmark_remove">Yer imini kaldır</string>
|
||||
<string name="bookmarks">Yer imleri</string>
|
||||
<string name="bookmark_removed">Yer imi kaldırıldı</string>
|
||||
<string name="bookmark_added">Yer imi eklendi</string>
|
||||
<string name="undo">Geri al</string>
|
||||
<string name="removed_from_history">Geçmişten kaldırıldı</string>
|
||||
<string name="dns_over_https">HTTPS üzerinden DNS</string>
|
||||
<string name="detect_reader_mode">Okuyucu modunu otomatik algıla</string>
|
||||
<string name="detect_reader_mode_summary">Manganın webtoon olup olmadığını otomatik olarak algıla</string>
|
||||
<string name="default_mode">Öntanımlı mod</string>
|
||||
</resources>
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d новий розділ</item>
|
||||
<item quantity="few">%1$d нових розділи</item>
|
||||
<item quantity="few">%1$d нові розділи</item>
|
||||
<item quantity="many">%1$d нових розділів</item>
|
||||
<item quantity="other">%1$d нових розділів</item>
|
||||
</plurals>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<string name="cache">Кеш</string>
|
||||
<string name="text_file_sizes">Б|кБ|МБ|ГБ|ТБ</string>
|
||||
<string name="standard">Стандартний</string>
|
||||
<string name="webtoon">Манхва</string>
|
||||
<string name="webtoon">Вебтун</string>
|
||||
<string name="read_mode">Режим читання</string>
|
||||
<string name="grid_size">Розмір сітки</string>
|
||||
<string name="search_on_s">Пошук по %s</string>
|
||||
@@ -89,7 +89,6 @@
|
||||
<string name="notifications">Сповіщення</string>
|
||||
<string name="enabled_d_of_d" tools:ignore="PluralsCandidate">Увімкнено %1$d з %2$d</string>
|
||||
<string name="new_chapters">Нові розділи</string>
|
||||
<string name="show_notification_new_chapters">Повідомляти про оновлення манґи, яку Ви читаєте</string>
|
||||
<string name="download">Завантажити</string>
|
||||
<string name="read_from_start">Читати з початку</string>
|
||||
<string name="restart">Перезавантажити</string>
|
||||
@@ -136,7 +135,6 @@
|
||||
<string name="checking_for_updates">Перевірка наявності оновлень…</string>
|
||||
<string name="update_check_failed">Не вдалося перевірити оновлення</string>
|
||||
<string name="no_update_available">Немає доступних оновлень</string>
|
||||
<string name="prefer_rtl_reader">Віддавати перевагу читанню справа наліво (←)</string>
|
||||
<string name="create_category">Нова категорія</string>
|
||||
<string name="scale_mode">Режим масштабування</string>
|
||||
<string name="zoom_mode_fit_center">Вмістити в екран</string>
|
||||
@@ -145,7 +143,6 @@
|
||||
<string name="zoom_mode_keep_start">Вихідний розмір</string>
|
||||
<string name="black_dark_theme">Чорна</string>
|
||||
<string name="black_dark_theme_summary">Споживає менше енергії на екранах AMOLED</string>
|
||||
<string name="restart_required">Потрібен перезапуск</string>
|
||||
<string name="backup_restore">Резервне копіювання та відновлення</string>
|
||||
<string name="data_restored">Відновлено</string>
|
||||
<string name="preparing_">Підготовка…</string>
|
||||
@@ -166,7 +163,7 @@
|
||||
<string name="sign_in">Увійти</string>
|
||||
<string name="auth_required">Увійдіть, щоб переглянути цей вміст</string>
|
||||
<string name="default_s">За замовчуванням: %s</string>
|
||||
<string name="_and_x_more">...і ще %1$d</string>
|
||||
<string name="_and_x_more">…і ще %1$d</string>
|
||||
<string name="next">Далі</string>
|
||||
<string name="protect_application_subtitle">Введіть пароль для запуску програми</string>
|
||||
<string name="confirm">Підтвердити</string>
|
||||
@@ -252,7 +249,6 @@
|
||||
<string name="text_categories_holder">Ви можете використовувати категорії для впорядкування своїх уподобань. Натисніть «+», щоб створити категорію</string>
|
||||
<string name="yesterday">Учора</string>
|
||||
<string name="right_to_left">Справа наліво (←)</string>
|
||||
<string name="prefer_rtl_reader_summary">Режим читання можна налаштувати окремо для кожної серії</string>
|
||||
<string name="create_backup">Створити резервну копію</string>
|
||||
<string name="restore_backup">Відновити з резервної копії</string>
|
||||
<string name="data_restored_success">Всі дані були відновлені</string>
|
||||
@@ -279,4 +275,23 @@
|
||||
<string name="suggestions_excluded_genres_summary">Укажіть жанри, які ви не хочете бачити в пропозиціях</string>
|
||||
<string name="download_slowdown_summary">Допомагає уникнути блокування вашої IP-адреси</string>
|
||||
<string name="chapters_will_removed_background">Розділи будуть видалені у фоновому режимі. Це може зайняти деякий час</string>
|
||||
<string name="check_new_chapters_title">Перевіряти наявність нових розділів і повідомляти про них</string>
|
||||
<string name="show_notification_new_chapters_on">Ви будете отримувати повідомлення про оновлення манґи, яку ви читаєте</string>
|
||||
<string name="notifications_enable">Увімкнути сповіщення</string>
|
||||
<string name="show_notification_new_chapters_off">Ви не будете отримувати повідомлення, але нові розділи будуть відображатися у списку</string>
|
||||
<string name="empty_favourite_categories">Немає улюблених категорій</string>
|
||||
<string name="name">Назва</string>
|
||||
<string name="edit">Змінити</string>
|
||||
<string name="edit_category">Змінити категорію</string>
|
||||
<string name="bookmark_add">Додати закладку</string>
|
||||
<string name="bookmark_remove">Видалити закладку</string>
|
||||
<string name="bookmarks">Закладки</string>
|
||||
<string name="bookmark_removed">Закладка видалена</string>
|
||||
<string name="bookmark_added">Додано закладку</string>
|
||||
<string name="undo">Скасувати</string>
|
||||
<string name="removed_from_history">Видалено з історії</string>
|
||||
<string name="dns_over_https">DNS через HTTPS</string>
|
||||
<string name="default_mode">Типовий режим</string>
|
||||
<string name="detect_reader_mode_summary">Автоматично визначати, чи є манга вебтуном</string>
|
||||
<string name="detect_reader_mode">Автовизначення режиму читання</string>
|
||||
</resources>
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="ThemeOverlay.Kotatsu.BottomSheetDialog" parent="ThemeOverlay.Material3.DayNight.BottomSheetDialog">
|
||||
<item name="android:navigationBarColor">@color/navigation_bar_scrim</item>
|
||||
<item name="android:windowLightNavigationBar">@bool/light_navigation_bar</item>
|
||||
<item name="colorControlHighlight">?colorSecondary</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -3,4 +3,5 @@
|
||||
<dimen name="grid_spacing">6dp</dimen>
|
||||
<dimen name="grid_spacing_outer">2dp</dimen>
|
||||
<dimen name="preferred_grid_width">140dp</dimen>
|
||||
<dimen name="bottom_sheet_width">420dp</dimen>
|
||||
</resources>
|
||||
@@ -20,5 +20,6 @@
|
||||
<color name="dim">#99000000</color>
|
||||
<color name="scrollbar">#66000000</color>
|
||||
<color name="selector_foreground">#29000000</color>
|
||||
<color name="divider_default">#1F000000</color>
|
||||
|
||||
</resources>
|
||||
@@ -26,4 +26,7 @@
|
||||
|
||||
<dimen name="search_suggestions_manga_height">124dp</dimen>
|
||||
<dimen name="search_suggestions_manga_spacing">4dp</dimen>
|
||||
|
||||
<dimen name="bottom_sheet_width">0dp</dimen>
|
||||
<dimen name="dialog_radius">8dp</dimen>
|
||||
</resources>
|
||||
@@ -168,7 +168,6 @@
|
||||
<string name="zoom_mode_keep_start">Keep at start</string>
|
||||
<string name="black_dark_theme">Black</string>
|
||||
<string name="black_dark_theme_summary">Uses less power on AMOLED screens</string>
|
||||
<string name="restart_required">Restart required</string>
|
||||
<string name="backup_restore">Backup and restore</string>
|
||||
<string name="create_backup">Create data backup</string>
|
||||
<string name="restore_backup">Restore from backup</string>
|
||||
|
||||
@@ -22,9 +22,26 @@
|
||||
|
||||
<!-- Bottom sheet -->
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu.BottomSheetDialog" parent="ThemeOverlay.Material3.DayNight.BottomSheetDialog">
|
||||
<item name="android:statusBarColor">@color/dim</item>
|
||||
<item name="colorControlHighlight">?colorSecondary</item>
|
||||
<style name="ThemeOverlay.Kotatsu.BottomSheetDialog" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
||||
<item name="bottomSheetStyle">@style/Widget.Kotatsu.BottomSheet.Modal</item>
|
||||
<item name="android:windowAnimationStyle">@style/Animation.Kotatsu.BottomSheetDialog</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.Kotatsu.BottomSheet.Modal" parent="Widget.Material3.BottomSheet.Modal">
|
||||
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.Kotatsu.BottomSheet</item>
|
||||
</style>
|
||||
|
||||
<style name="ShapeAppearanceOverlay.Kotatsu.BottomSheet" parent="">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSizeTopRight">@dimen/dialog_radius</item>
|
||||
<item name="cornerSizeTopLeft">@dimen/dialog_radius</item>
|
||||
<item name="cornerSizeBottomRight">0dp</item>
|
||||
<item name="cornerSizeBottomLeft">0dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Animation.Kotatsu.BottomSheetDialog" parent="Animation.AppCompat.Dialog">
|
||||
<item name="android:windowEnterAnimation">@anim/bottom_sheet_slide_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/bottom_sheet_slide_out</item>
|
||||
</style>
|
||||
|
||||
<!-- Widget styles -->
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
<item name="colorErrorContainer">@color/errorContainer</item>
|
||||
<item name="colorOnErrorContainer">@color/onErrorContainer</item>
|
||||
|
||||
<item name="android:divider">@color/divider_default</item>
|
||||
|
||||
<!-- Ripples -->
|
||||
<item name="colorControlHighlight">?attr/colorSurfaceVariant</item>
|
||||
|
||||
@@ -86,9 +88,11 @@
|
||||
<!-- Monet theme only support S+ -->
|
||||
<style name="Theme.Kotatsu.Monet" />
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu" parent="ThemeOverlay.Material3.Light" />
|
||||
<style name="Theme.Kotatsu.Amoled" />
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu.AMOLED" parent="" />
|
||||
<style name="Theme.Kotatsu.Monet.Amoled" />
|
||||
|
||||
<style name="ThemeOverlay.Kotatsu" parent="ThemeOverlay.Material3.Light" />
|
||||
|
||||
<style name="Theme.Kotatsu.Dialog" parent="">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
|
||||
Reference in New Issue
Block a user