Compare commits

..

20 Commits

Author SHA1 Message Date
Koitharu
fc2820ec11 Update version to v3.3 2022-05-20 19:12:58 +03:00
Zakhar Timoshenko
312fb033e0 Fix weird toolbar in category edit activity 2022-05-20 18:40:53 +03:00
Koitharu
18bc4dc739 Merge branch 'devel' of github.com:nv95/Kotatsu into devel 2022-05-20 12:16:22 +03:00
Artem
2b61b27271 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Co-authored-by: Artem <artem@molotov.work>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/uk/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-05-20 12:15:03 +03:00
Koitharu
58c9f75b91 Fix tags order in filter 2022-05-20 12:14:23 +03:00
Koitharu
790f1fb8a3 Update parsers 2022-05-20 12:06:26 +03:00
Zakhar Timoshenko
5c4f3f7fe4 Revert onCreateDialog method
This thing is really needed
2022-05-18 23:35:58 +03:00
Zakhar Timoshenko
86ead09080 Revert accidentally removed style 2022-05-18 22:20:54 +03:00
Zakhar Timoshenko
0932507346 Bottom sheet improvements 2022-05-18 22:14:14 +03:00
Koitharu
21f7b7120a Use collator for tags sorting 2022-05-18 11:27:07 +03:00
Koitharu
473135bfc5 Apply theme changing without restarting 2022-05-17 16:26:04 +03:00
Koitharu
ce7960e5e9 Recreate all activities on theme changed 2022-05-17 13:23:03 +03:00
Koitharu
17c440ee43 Fix marking sources as new 2022-05-17 11:31:06 +03:00
Koitharu
5d881ca154 New global search activity 2022-05-17 11:19:55 +03:00
Koitharu
e4b29b3ff9 Cleanup strings 2022-05-15 17:11:40 +03:00
Luiz-bro
046aaa0649 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (297 of 297 strings)

Co-authored-by: Luiz-bro <luiznneto1@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt_BR/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
Dpper
f653c74ce8 Translated using Weblate (Ukrainian)
Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/uk/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
kuragehime
0c73c55b9d Translated using Weblate (Japanese)
Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (287 of 287 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
Oğuz Ersen
8dec54e96f Translated using Weblate (Turkish)
Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: Oğuz Ersen <oguz@ersen.moe>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
J. Lavoie
859ae966c8 Translated using Weblate (Finnish)
Currently translated at 99.6% (296 of 297 strings)

Translated using Weblate (French)

Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (German)

Currently translated at 100.0% (297 of 297 strings)

Translated using Weblate (Finnish)

Currently translated at 99.6% (294 of 295 strings)

Translated using Weblate (French)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (German)

Currently translated at 100.0% (295 of 295 strings)

Translated using Weblate (Finnish)

Currently translated at 99.6% (287 of 288 strings)

Translated using Weblate (French)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (German)

Currently translated at 100.0% (288 of 288 strings)

Translated using Weblate (French)

Currently translated at 100.0% (284 of 284 strings)

Co-authored-by: J. Lavoie <j.lavoie@net-c.ca>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/de/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fi/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/fr/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/it/
Translation: Kotatsu/Strings
2022-05-15 17:04:21 +03:00
77 changed files with 929 additions and 329 deletions

1
.gitignore vendored
View File

@@ -6,6 +6,7 @@
/.idea/dictionaries
/.idea/modules.xml
/.idea/misc.xml
/.idea/discord.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml

View File

@@ -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'

View File

@@ -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"

View File

@@ -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())

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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() }
}
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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())

View File

@@ -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)
}
}
}

View File

@@ -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) }

View File

@@ -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) {

View File

@@ -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

View 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()) }

View File

@@ -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)
}

View File

@@ -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,

View File

@@ -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()) }
}

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}

View File

@@ -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)
}
}
}

View File

@@ -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()
}
}

View File

@@ -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()
}
}
}

View File

@@ -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
}
}

View File

@@ -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)

View File

@@ -51,6 +51,9 @@ class SourcesSettingsViewModel(
} else {
settings.hiddenSources + source.name
}
if (isEnabled) {
settings.markKnownSources(setOf(source))
}
buildList()
}

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}
}
}
}

View 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>

View 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>

View 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>

View File

@@ -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>

View File

@@ -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"

View 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>

View 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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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 -->

View File

@@ -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>