Compare commits

..

63 Commits

Author SHA1 Message Date
245e87256b Merge branch 'master' of github.com:KotatsuApp/Kotatsu 2022-10-20 10:38:34 +03:00
ed8c69037f Merge branch 'devel' 2022-10-20 10:31:23 +03:00
Eric
3f76d22d67 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (398 of 398 strings)

Co-authored-by: Eric <hamburger1024@mailbox.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-10-20 10:05:11 +03:00
980988e684 Translated using Weblate (Russian)
Currently translated at 99.7% (397 of 398 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2022-10-20 10:05:11 +03:00
Allan Nordhøy
347811abb6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 82.9% (330 of 398 strings)

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nb_NO/
Translation: Kotatsu/Strings
2022-10-20 10:05:11 +03:00
kuragehime
ccb8b0c8e7 Translated using Weblate (Japanese)
Currently translated at 100.0% (397 of 397 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-10-20 10:05:11 +03:00
Eric
18137ab48e Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (397 of 397 strings)

Co-authored-by: Eric <lessonaeamazon@paranoid.email>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-10-20 10:05:11 +03:00
Dpper
a9f435ae3d Translated using Weblate (Ukrainian)
Currently translated at 100.0% (397 of 397 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (8 of 8 strings)

Co-authored-by: Dpper <ruslan20020401@gmail.com>
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-10-20 10:05:11 +03:00
0758cfef64 Translated using Weblate (Russian)
Currently translated at 99.7% (396 of 397 strings)

Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2022-10-20 10:05:11 +03:00
06d1d56448 Update parsers 2022-10-20 10:04:52 +03:00
07c70eaccc Update strings in list mode bs 2022-10-20 09:25:20 +03:00
5ad6413952 Update parsers 2022-10-19 15:28:42 +03:00
Shippo
0b9d9ac7f2 Translated using Weblate (Arabic)
Currently translated at 14.9% (59 of 395 strings)

Translated using Weblate (Arabic)

Currently translated at 6.0% (24 of 395 strings)

Translated using Weblate (Arabic)

Currently translated at 25.0% (2 of 8 strings)

Added translation using Weblate (Arabic)

Added translation using Weblate (Arabic)

Co-authored-by: Shippo <shiposhouyou@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/ar/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ar/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-10-19 15:28:31 +03:00
3ab87027ab Translated using Weblate (Russian)
Currently translated at 99.7% (394 of 395 strings)

Co-authored-by: Koitharu <nvasya95@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2022-10-19 15:28:31 +03:00
Eric
7545a774ba Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (395 of 395 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (392 of 392 strings)

Co-authored-by: Eric <hamburger1024@mailbox.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-10-19 15:28:31 +03:00
db16eb8e29 Translated using Weblate (Russian)
Currently translated at 99.7% (391 of 392 strings)

Co-authored-by: Zakhar Timoshenko <vp1984tanki@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ru/
Translation: Kotatsu/Strings
2022-10-19 15:28:31 +03:00
Kyoya
e5a27a7c6f Translated using Weblate (Turkish)
Currently translated at 98.4% (386 of 392 strings)

Co-authored-by: Kyoya <thelol9181@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/tr/
Translation: Kotatsu/Strings
2022-10-19 15:28:31 +03:00
J. Lavoie
d26bc102d1 Translated using Weblate (French)
Currently translated at 100.0% (395 of 395 strings)

Translated using Weblate (French)

Currently translated at 100.0% (392 of 392 strings)

Translated using Weblate (German)

Currently translated at 94.1% (369 of 392 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/fr/
Translation: Kotatsu/Strings
2022-10-19 15:28:31 +03:00
Eduardo Malaspina
fc6a8afd93 Translated using Weblate (Spanish)
Currently translated at 99.2% (389 of 392 strings)

Co-authored-by: Eduardo Malaspina <vaio0@swismail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/es/
Translation: Kotatsu/Strings
2022-10-19 15:28:31 +03:00
5a9d401446 Ui updates 2022-10-19 12:29:57 +03:00
77ac40b445 Option to clear new chapters counters 2022-10-19 11:26:45 +03:00
a29454f672 Incognito mode indicator 2022-10-19 11:05:11 +03:00
80ee7c8e54 Add installation id to acra reports 2022-10-19 10:26:12 +03:00
fb202f80a5 User-friendly message for HttpStatusException 2022-10-19 10:10:25 +03:00
45dbd5aa44 Fix crash on page loading 2022-10-16 10:41:53 +03:00
ee65251bf5 Update parsers 2022-10-16 10:38:23 +03:00
eaeb11f9ce Fix crash on page loading 2022-10-16 10:36:02 +03:00
2f74633abb Fix popup dark color text on dark dynamic theme 2022-10-16 00:06:57 +03:00
0f346dc725 Fix paddings 2022-10-16 00:05:57 +03:00
1b92848964 Wrong fourth screenshot... 2022-10-15 23:00:07 +03:00
3d91583585 Reduced screenshot resolution 2022-10-15 22:58:31 +03:00
f76d9fa3e4 Update phone screenshots 2022-10-15 22:51:12 +03:00
b00b2e406e Adjust app theme and add new empty states 2022-10-15 18:18:59 +03:00
74717e2b93 Handle offline mode in shelf 2022-10-14 17:04:04 +03:00
9b54ed6bc7 Show local section in shelf 2022-10-14 15:34:58 +03:00
7b36c64b34 Hide feed section if tracker is disabled 2022-10-14 15:02:50 +03:00
da09884136 Add updated manga to shelf 2022-10-14 14:49:53 +03:00
64aaf37556 Show sources names in onboarding 2022-10-14 13:36:33 +03:00
11104223eb Disable under-scaling in webtoon zoom 2022-10-14 13:14:04 +03:00
0c119bc137 Update parsers 2022-10-12 19:29:41 +03:00
5c058e626b Merge branch 'VietAnh14-webtoon_zoom' into devel 2022-10-12 12:52:50 +03:00
2005ae2bf3 Merge branch 'devel' of github.com:KotatsuApp/Kotatsu into VietAnh14-webtoon_zoom 2022-10-12 12:38:59 +03:00
d0650c7cf4 Refactor webtoon zoom option 2022-10-12 12:37:37 +03:00
Allan Nordhøy
2df4e6480a Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.0% (324 of 390 strings)

Translated using Weblate (English)

Currently translated at 100.0% (390 of 390 strings)

Co-authored-by: Allan Nordhøy <epost@anotheragency.no>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/en/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/nb_NO/
Translation: Kotatsu/Strings
2022-10-12 12:28:54 +03:00
J. Lavoie
017a1686dc Translated using Weblate (French)
Currently translated at 100.0% (390 of 390 strings)

Translated using Weblate (German)

Currently translated at 94.6% (369 of 390 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/fr/
Translation: Kotatsu/Strings
2022-10-12 12:28:54 +03:00
Cường Bá
279dc03695 Translated using Weblate (Vietnamese)
Currently translated at 62.5% (5 of 8 strings)

Added translation using Weblate (Vietnamese)

Co-authored-by: Cường Bá <cuongba956@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/vi/
Translation: Kotatsu/plurals
2022-10-12 12:28:54 +03:00
kuragehime
c8c482f692 Translated using Weblate (Japanese)
Currently translated at 100.0% (390 of 390 strings)

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translation: Kotatsu/Strings
2022-10-12 12:28:54 +03:00
Eric
02a0e3ebcd Translated using Weblate (Chinese (Simplified))
Currently translated at 99.4% (388 of 390 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (390 of 390 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (389 of 389 strings)

Co-authored-by: Eric <hamburger1024@mailbox.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hans/
Translation: Kotatsu/Strings
2022-10-12 12:28:54 +03:00
Daniel Rozario
fc0b3f3b38 Translated using Weblate (Bengali)
Currently translated at 2.5% (10 of 389 strings)

Translated using Weblate (Bengali)

Currently translated at 37.5% (3 of 8 strings)

Added translation using Weblate (Bengali)

Added translation using Weblate (Bengali)

Co-authored-by: Daniel Rozario <rozario@tuta.io>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/bn/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/bn/
Translation: Kotatsu/Strings
Translation: Kotatsu/plurals
2022-10-12 12:28:54 +03:00
vianh
2925900214 Add disable webtoon zoom setting 2022-10-11 18:13:46 +07:00
vianh
eae370e41c Adjust zoom focus point 2022-10-07 22:17:53 +07:00
vianh
d0338a604a Minor adjustment for webtoon scale 2022-10-07 16:15:53 +07:00
vianh
e22b98b476 Support zoom for webtoon mode 2022-10-07 11:13:30 +07:00
4d838d290d Update dependencies 2022-10-03 08:41:32 +03:00
048efdf59f Fix crash on slider 2022-10-03 08:07:25 +03:00
65dbc6b8e5 Fix crash on slider 2022-10-03 08:02:49 +03:00
af2adeba13 Fix opening fingerprint dialog 2022-10-01 12:19:36 +03:00
93c6bec452 Fix widgets colors 2022-10-01 10:34:25 +03:00
c944044465 Update version 2022-09-22 17:38:08 +03:00
8a63ca2310 Fix coroutines cancellation 2022-09-22 17:33:40 +03:00
12e5e3b35e Update gitignore 2022-09-22 16:53:27 +03:00
553a85ef86 Widget theme fix #225 2022-09-22 16:49:38 +03:00
de7012cabf Change acra sender to http 2022-09-15 08:36:20 +03:00
109 changed files with 1808 additions and 786 deletions

1
.gitignore vendored
View File

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

6
.idea/compiler.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

2
.idea/kotlinc.xml generated
View File

@@ -4,6 +4,6 @@
<option name="jvmTarget" value="1.8" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.7.10" />
<option name="version" value="1.7.20" />
</component>
</project>

View File

@@ -15,8 +15,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 33
versionCode 499
versionName '4.0-rc1'
versionCode 500
versionName '4.0'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -83,7 +83,7 @@ afterEvaluate {
}
}
dependencies {
implementation('com.github.KotatsuApp:kotatsu-parsers:b1990c7918') {
implementation('com.github.KotatsuApp:kotatsu-parsers:a1441e7ed7') {
exclude group: 'org.json', module: 'json'
}
@@ -103,7 +103,7 @@ dependencies {
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.7.0-rc01'
implementation 'com.google.android.material:material:1.7.0'
//noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
@@ -123,8 +123,8 @@ dependencies {
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'io.coil-kt:coil-base:2.2.1'
implementation 'io.coil-kt:coil-svg:2.2.1'
implementation 'io.coil-kt:coil-base:2.2.2'
implementation 'io.coil-kt:coil-svg:2.2.2'
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:0ff0278f0f'
implementation 'com.github.solkin:disk-lru-cache:1.4'
@@ -134,7 +134,7 @@ dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.json:json:20220320'
testImplementation 'org.json:json:20220924'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
androidTestImplementation 'androidx.test:runner:1.4.0'

View File

@@ -68,6 +68,9 @@
<activity
android:name="org.koitharu.kotatsu.history.ui.HistoryActivity"
android:label="@string/history" />
<activity
android:name="org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity"
android:label="@string/updates" />
<activity
android:name="org.koitharu.kotatsu.favourites.ui.FavouritesActivity"
android:label="@string/favourites" />

View File

@@ -10,7 +10,6 @@ import androidx.hilt.work.HiltWorkerFactory
import androidx.room.InvalidationTracker
import androidx.work.Configuration
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.acra.ReportField
@@ -25,6 +24,7 @@ import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import javax.inject.Inject
@HiltAndroidApp
class KotatsuApp : Application(), Configuration.Provider {
@@ -72,6 +72,7 @@ class KotatsuApp : Application(), Configuration.Provider {
}
reportContent = listOf(
ReportField.PACKAGE_NAME,
ReportField.INSTALLATION_ID,
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.ANDROID_VERSION,

View File

@@ -0,0 +1,22 @@
package org.koitharu.kotatsu.base.ui
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
interface DefaultActivityLifecycleCallbacks : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = 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) = Unit
}

View File

@@ -1,14 +1,14 @@
package org.koitharu.kotatsu.base.ui.util
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
import java.util.*
import org.koitharu.kotatsu.base.ui.DefaultActivityLifecycleCallbacks
import java.util.WeakHashMap
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ActivityRecreationHandle @Inject constructor() : ActivityLifecycleCallbacks {
class ActivityRecreationHandle @Inject constructor() : DefaultActivityLifecycleCallbacks {
private val activities = WeakHashMap<Activity, Unit>()
@@ -16,16 +16,6 @@ class ActivityRecreationHandle @Inject constructor() : ActivityLifecycleCallback
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)
}

View File

@@ -18,8 +18,6 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ElementsIntoSet
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
import kotlinx.coroutines.Dispatchers
import okhttp3.CookieJar
import okhttp3.OkHttpClient
@@ -40,9 +38,12 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.search.ui.MangaSuggestionsProvider
import org.koitharu.kotatsu.settings.backup.BackupObserver
import org.koitharu.kotatsu.sync.domain.SyncController
import org.koitharu.kotatsu.utils.IncognitoModeIndicator
import org.koitharu.kotatsu.utils.ext.isLowRamDevice
import org.koitharu.kotatsu.utils.image.CoilImageGetter
import org.koitharu.kotatsu.widget.WidgetUpdater
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
@@ -152,9 +153,11 @@ interface AppModule {
fun provideActivityLifecycleCallbacks(
appProtectHelper: AppProtectHelper,
activityRecreationHandle: ActivityRecreationHandle,
incognitoModeIndicator: IncognitoModeIndicator,
): Set<@JvmSuppressWildcards Application.ActivityLifecycleCallbacks> = arraySetOf(
appProtectHelper,
activityRecreationHandle,
incognitoModeIndicator,
)
}
}

View File

@@ -0,0 +1,64 @@
package org.koitharu.kotatsu.core.os
import android.content.Context
import android.net.ConnectivityManager.NetworkCallback
import android.net.Network
import android.net.NetworkRequest
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import org.koitharu.kotatsu.utils.ext.connectivityManager
import org.koitharu.kotatsu.utils.ext.isNetworkAvailable
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NetworkStateObserver @Inject constructor(
@ApplicationContext context: Context,
) : StateFlow<Boolean> {
private val connectivityManager = context.connectivityManager
override val replayCache: List<Boolean>
get() = listOf(value)
override var value: Boolean = connectivityManager.isNetworkAvailable
override suspend fun collect(collector: FlowCollector<Boolean>): Nothing {
collector.emit(value)
while (true) {
observeImpl().collect(collector)
}
}
private fun observeImpl() = callbackFlow<Boolean> {
val request = NetworkRequest.Builder().build()
val callback = FlowNetworkCallback(this)
connectivityManager.registerNetworkCallback(request, callback)
awaitClose {
connectivityManager.unregisterNetworkCallback(callback)
}
}
inner class FlowNetworkCallback(
private val producerScope: ProducerScope<Boolean>,
) : NetworkCallback() {
override fun onAvailable(network: Network) = update()
override fun onLost(network: Network) = update()
override fun onUnavailable() = update()
private fun update() {
val newValue = connectivityManager.isNetworkAvailable
if (value != newValue) {
value = newValue
producerScope.trySendBlocking(newValue)
}
}
}
}

View File

@@ -218,6 +218,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST)
set(value) = prefs.edit { putEnumValue(KEY_LOCAL_LIST_ORDER, value) }
val isWebtoonZoomEnable: Boolean
get() = prefs.getBoolean(KEY_WEBTOON_ZOOM, true)
fun isPagesPreloadAllowed(cm: ConnectivityManager): Boolean {
return when (prefs.getString(KEY_PAGES_PRELOAD, null)?.toIntOrNull()) {
NETWORK_ALWAYS -> true
@@ -337,6 +340,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_SHORTCUTS = "dynamic_shortcuts"
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
const val KEY_LOCAL_LIST_ORDER = "local_order"
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
// About
const val KEY_APP_UPDATE = "app_update"

View File

@@ -34,7 +34,7 @@ fun chapterListItemAD(
when (item.status) {
FLAG_UNREAD -> {
binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_default)
binding.textViewNumber.setTextColor(context.getThemeColor(android.R.attr.textColorSecondaryInverse))
binding.textViewNumber.setTextColor(context.getThemeColor(com.google.android.material.R.attr.colorOnTertiary))
}
FLAG_CURRENT -> {
binding.textViewNumber.setBackgroundResource(R.drawable.bg_badge_accent)
@@ -53,4 +53,4 @@ fun chapterListItemAD(
binding.imageViewDownloaded.isVisible = item.hasFlag(FLAG_DOWNLOADED)
binding.imageViewNew.isVisible = item.hasFlag(FLAG_NEW)
}
}
}

View File

@@ -61,7 +61,7 @@ class ExploreViewModel @Inject constructor(
sources.mapTo(result) { ExploreItem.Source(it) }
} else {
result += ExploreItem.EmptyHint(
icon = R.drawable.ic_empty_search,
icon = R.drawable.ic_empty_common,
textPrimary = R.string.no_manga_sources,
textSecondary = R.string.no_manga_sources_text,
actionStringRes = R.string.manage,

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.explore.ui.model
import android.net.Uri
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -73,6 +72,7 @@ sealed interface ExploreItem : ListModel {
}
}
@Deprecated("")
class EmptyHint(
@DrawableRes icon: Int,
@StringRes textPrimary: Int,
@@ -81,4 +81,4 @@ sealed interface ExploreItem : ListModel {
) : EmptyState(icon, textPrimary, textSecondary, actionStringRes), ExploreItem
object Loading : ExploreItem
}
}

View File

@@ -6,24 +6,23 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.databinding.DialogListModeBinding
import org.koitharu.kotatsu.utils.ext.setValueRounded
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
import javax.inject.Inject
@AndroidEntryPoint
class ListModeSelectDialog :
AlertDialogFragment<DialogListModeBinding>(),
CheckableButtonGroup.OnCheckedChangeListener,
Slider.OnChangeListener {
class ListModeBottomSheet :
BaseBottomSheet<DialogListModeBinding>(),
Slider.OnChangeListener,
MaterialButtonToggleGroup.OnButtonCheckedListener {
@Inject
lateinit var settings: AppSettings
@@ -33,13 +32,6 @@ class ListModeSelectDialog :
container: ViewGroup?,
) = DialogListModeBinding.inflate(inflater, container, false)
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
return super.onBuildDialog(builder)
.setTitle(R.string.list_mode)
.setPositiveButton(R.string.done, null)
.setCancelable(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val mode = settings.listMode
@@ -53,10 +45,10 @@ class ListModeSelectDialog :
binding.sliderGrid.setValueRounded(settings.gridSize.toFloat())
binding.sliderGrid.addOnChangeListener(this)
binding.checkableGroup.onCheckedChangeListener = this
binding.checkableGroup.addOnButtonCheckedListener(this)
}
override fun onCheckedChanged(group: CheckableButtonGroup, checkedId: Int) {
override fun onButtonChecked(group: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean) {
val mode = when (checkedId) {
R.id.button_list -> ListMode.LIST
R.id.button_list_detailed -> ListMode.DETAILED_LIST
@@ -78,6 +70,6 @@ class ListModeSelectDialog :
private const val TAG = "ListModeSelectDialog"
fun show(fm: FragmentManager) = ListModeSelectDialog().show(fm, TAG)
fun show(fm: FragmentManager) = ListModeBottomSheet().show(fm, TAG)
}
}

View File

@@ -17,9 +17,10 @@ class MangaListMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_list_mode -> {
ListModeSelectDialog.show(fragment.childFragmentManager)
ListModeBottomSheet.show(fragment.childFragmentManager)
true
}
else -> false
}
}
}

View File

@@ -0,0 +1,23 @@
package org.koitharu.kotatsu.list.ui.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.databinding.ItemEmptyCardBinding
import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.setTextAndVisible
fun emptyHintAD(
listener: ListStateHolderListener,
) = adapterDelegateViewBinding<EmptyHint, ListModel, ItemEmptyCardBinding>(
{ inflater, parent -> ItemEmptyCardBinding.inflate(inflater, parent, false) },
) {
binding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() }
bind {
binding.icon.setImageResource(item.icon)
binding.textPrimary.setText(item.textPrimary)
binding.textSecondary.setTextAndVisible(item.textSecondary)
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
}
}

View File

@@ -0,0 +1,14 @@
package org.koitharu.kotatsu.list.ui.model
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
class EmptyHint(
@DrawableRes icon: Int,
@StringRes textPrimary: Int,
@StringRes textSecondary: Int,
@StringRes actionStringRes: Int,
) : EmptyState(icon, textPrimary, textSecondary, actionStringRes) {
fun toState() = EmptyState(icon, textPrimary, textSecondary, actionStringRes)
}

View File

@@ -15,15 +15,21 @@ import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.util.size
import androidx.core.view.*
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import androidx.transition.TransitionManager
import com.google.android.material.R as materialR
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.LayoutParams.*
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL
import com.google.android.material.appbar.AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
@@ -34,7 +40,6 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.base.ui.widgets.SlidingBottomNavigationView
import org.koitharu.kotatsu.databinding.ActivityMainBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.shelf.ui.ShelfFragment
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.model.Manga
@@ -48,11 +53,18 @@ import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
import org.koitharu.kotatsu.settings.onboard.OnboardDialogFragment
import org.koitharu.kotatsu.shelf.ui.ShelfFragment
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.VoiceInputContract
import org.koitharu.kotatsu.utils.ext.*
import kotlin.text.Typography.dagger
import org.koitharu.kotatsu.utils.ext.drawableEnd
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.hideKeyboard
import org.koitharu.kotatsu.utils.ext.resolve
import org.koitharu.kotatsu.utils.ext.scaleUpActivityOptionsOf
import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.utils.ext.tryLaunch
import com.google.android.material.R as materialR
private const val TAG_SEARCH = "search"
@@ -116,6 +128,7 @@ class MainActivity :
viewModel.isLoading.observe(this, this::onLoadingStateChanged)
viewModel.isResumeEnabled.observe(this, this::onResumeEnabledChanged)
viewModel.counters.observe(this, ::onCountersChanged)
viewModel.isFeedAvailable.observe(this, ::onFeedAvailabilityChanged)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
@@ -139,11 +152,7 @@ class MainActivity :
}
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
if (fragment is ShelfFragment) {
binding.fab?.show()
} else {
binding.fab?.hide()
}
adjustFabVisibility(topFragment = fragment)
if (fromUser) {
binding.appbar.setExpanded(true)
}
@@ -261,6 +270,10 @@ class MainActivity :
}
}
private fun onFeedAvailabilityChanged(isFeedAvailable: Boolean) {
navigationDelegate.setItemVisibility(R.id.nav_feed, isFeedAvailable)
}
private fun onLoadingStateChanged(isLoading: Boolean) {
binding.fab?.isEnabled = !isLoading
}
@@ -312,18 +325,18 @@ class MainActivity :
topFragment: Fragment? = navigationDelegate.primaryFragment,
isSearchOpened: Boolean = isSearchOpened(),
) {
val fab = binding.fab
val fab = binding.fab ?: return
if (
isResumeEnabled &&
!actionModeDelegate.isActionModeStarted &&
!isSearchOpened &&
topFragment is ShelfFragment
) {
if (fab?.isVisible == false) {
if (!fab.isVisible) {
fab.show()
}
} else {
if (fab?.isVisible == true) {
if (fab.isVisible) {
fab.hide()
}
}

View File

@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.main.ui
import android.os.Bundle
import android.view.MenuItem
import androidx.annotation.IdRes
import androidx.core.view.iterator
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
@@ -10,10 +11,10 @@ import com.google.android.material.navigation.NavigationBarView
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.explore.ui.ExploreFragment
import org.koitharu.kotatsu.shelf.ui.ShelfFragment
import org.koitharu.kotatsu.settings.tools.ToolsFragment
import org.koitharu.kotatsu.tracker.ui.FeedFragment
import java.util.*
import org.koitharu.kotatsu.shelf.ui.ShelfFragment
import org.koitharu.kotatsu.tracker.ui.feed.FeedFragment
import java.util.LinkedList
private const val TAG_PRIMARY = "primary"
@@ -62,6 +63,14 @@ class MainNavigationDelegate(
}
}
fun setItemVisibility(@IdRes itemId: Int, isVisible: Boolean) {
val item = navBar.menu.findItem(itemId) ?: return
item.isVisible = isVisible
if (item.isChecked && !isVisible) {
navBar.selectedItemId = firstItem()?.itemId ?: return
}
}
fun addOnFragmentChangedListener(listener: OnFragmentChangedListener) {
listeners.add(listener)
}
@@ -96,6 +105,14 @@ class MainNavigationDelegate(
listeners.forEach { it.onFragmentChanged(fragment, fromUser) }
}
private fun firstItem(): MenuItem? {
val menu = navBar.menu
for (item in menu) {
if (item.isVisible) return item
}
return null
}
interface OnFragmentChangedListener {
fun onFragmentChanged(fragment: Fragment, fromUser: Boolean)

View File

@@ -4,7 +4,6 @@ import android.util.SparseIntArray
import androidx.core.util.set
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import org.koitharu.kotatsu.R
@@ -12,6 +11,8 @@ import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.github.AppUpdateRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.sync.domain.SyncController
@@ -19,6 +20,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import javax.inject.Inject
@HiltViewModel
class MainViewModel @Inject constructor(
@@ -27,6 +29,7 @@ class MainViewModel @Inject constructor(
private val trackingRepository: TrackingRepository,
syncController: SyncController,
database: MangaDatabase,
private val settings: AppSettings,
) : BaseViewModel() {
val onOpenReader = SingleLiveEvent<Manga>()
@@ -35,6 +38,12 @@ class MainViewModel @Inject constructor(
.observeHasItems()
.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, false)
val isFeedAvailable = settings.observeAsLiveData(
context = viewModelScope.coroutineContext + Dispatchers.Default,
key = AppSettings.KEY_TRACKER_ENABLED,
valueProducer = { isTrackerEnabled },
)
val counters = combine(
appUpdateRepository.observeAvailableUpdate(),
trackingRepository.observeUpdatedMangaCount(),

View File

@@ -1,16 +1,16 @@
package org.koitharu.kotatsu.main.ui.protect
import android.app.Activity
import android.app.Application
import android.content.Intent
import android.os.Bundle
import org.acra.dialog.CrashReportDialog
import org.koitharu.kotatsu.base.ui.DefaultActivityLifecycleCallbacks
import org.koitharu.kotatsu.core.prefs.AppSettings
import javax.inject.Inject
import javax.inject.Singleton
import org.acra.dialog.CrashReportDialog
import org.koitharu.kotatsu.core.prefs.AppSettings
@Singleton
class AppProtectHelper @Inject constructor(private val settings: AppSettings) : Application.ActivityLifecycleCallbacks {
class AppProtectHelper @Inject constructor(private val settings: AppSettings) : DefaultActivityLifecycleCallbacks {
private var isUnlocked = settings.appPassword.isNullOrEmpty()
@@ -27,16 +27,6 @@ class AppProtectHelper @Inject constructor(private val settings: AppSettings) :
}
}
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) {
if (activity !is ProtectActivity && activity.isFinishing && activity.isTaskRoot) {
restoreLock()

View File

@@ -7,14 +7,16 @@ import android.net.Uri
import androidx.collection.LongSparseArray
import androidx.collection.set
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
import java.util.zip.ZipFile
import javax.inject.Inject
import kotlinx.coroutines.*
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.OkHttpClient
@@ -30,7 +32,15 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.await
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.utils.ext.connectivityManager
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.progress.ProgressDeferred
import java.io.File
import java.util.LinkedList
import java.util.concurrent.atomic.AtomicInteger
import java.util.zip.ZipFile
import javax.inject.Inject
import kotlin.coroutines.AbstractCoroutineContextElement
import kotlin.coroutines.CoroutineContext
private const val PROGRESS_UNDEFINED = -1f
private const val PREFETCH_LIMIT_DEFAULT = 10
@@ -43,7 +53,7 @@ class PageLoader @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory,
) : Closeable {
val loaderScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val loaderScope = CoroutineScope(SupervisorJob() + InternalErrorHandler() + Dispatchers.Default)
private val connectivityManager = context.connectivityManager
private val tasks = LongSparseArray<ProgressDeferred<File, Float>>()
@@ -197,4 +207,13 @@ class PageLoader @Inject constructor(
val deferred = CompletableDeferred(file)
return ProgressDeferred(deferred, emptyProgressFlow)
}
private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
CoroutineExceptionHandler {
override fun handleException(context: CoroutineContext, exception: Throwable) {
exception.printStackTraceDebug()
}
}
}

View File

@@ -106,6 +106,12 @@ class ReaderViewModel @AssistedInject constructor(
valueProducer = { isReaderBarEnabled },
)
val isWebtoonZoomEnabled = settings.observeAsLiveData(
context = viewModelScope.coroutineContext + Dispatchers.Default,
key = AppSettings.KEY_WEBTOON_ZOOM,
valueProducer = { isWebtoonZoomEnable },
)
val readerSettings = ReaderSettings(
parentScope = viewModelScope,
settings = settings,

View File

@@ -2,9 +2,13 @@ package org.koitharu.kotatsu.reader.ui.config
import android.content.SharedPreferences
import androidx.lifecycle.MediatorLiveData
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
@@ -60,7 +64,7 @@ class ReaderSettings(
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == AppSettings.KEY_ZOOM_MODE || key == AppSettings.KEY_PAGES_NUMBERS) {
if (key == AppSettings.KEY_ZOOM_MODE || key == AppSettings.KEY_PAGES_NUMBERS || key == AppSettings.KEY_WEBTOON_ZOOM) {
notifyChanged()
}
}

View File

@@ -132,7 +132,7 @@ class PageHolderDelegate(
callback.onImageReady(file.toUri())
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
} catch (e: Throwable) {
state = State.ERROR
error = e
callback.onError(e)

View File

@@ -35,6 +35,10 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
adapter = webtoonAdapter
addOnPageScrollListener(PageScrollListener())
}
viewModel.isWebtoonZoomEnabled.observe(viewLifecycleOwner) {
binding.frame.isZoomEnable = it
}
}
override fun onDestroyView() {

View File

@@ -0,0 +1,213 @@
package org.koitharu.kotatsu.reader.ui.pager.webtoon
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.FrameLayout
import android.widget.OverScroller
import androidx.core.view.GestureDetectorCompat
private const val MAX_SCALE = 2.5f
private const val MIN_SCALE = 1f // under-scaling disabled due to buggy nested scroll
class WebtoonScalingFrame @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyles: Int = 0,
) : FrameLayout(context, attrs, defStyles), ScaleGestureDetector.OnScaleGestureListener {
private val targetChild by lazy(LazyThreadSafetyMode.NONE) { getChildAt(0) }
private val scaleDetector = ScaleGestureDetector(context, this)
private val gestureDetector = GestureDetectorCompat(context, GestureListener())
private val overScroller = OverScroller(context, AccelerateDecelerateInterpolator())
private val transformMatrix = Matrix()
private val matrixValues = FloatArray(9)
private val scale
get() = matrixValues[Matrix.MSCALE_X]
private val transX
get() = halfWidth * (scale - 1f) + matrixValues[Matrix.MTRANS_X]
private val transY
get() = halfHeight * (scale - 1f) + matrixValues[Matrix.MTRANS_Y]
private var halfWidth = 0f
private var halfHeight = 0f
private val translateBounds = RectF()
private val targetHitRect = Rect()
private var pendingScroll = 0
var isZoomEnable = true
set(value) {
field = value
if (scale != 1f) {
scaleChild(1f, halfWidth, halfHeight)
}
}
init {
syncMatrixValues()
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (!isZoomEnable || ev == null) {
return super.dispatchTouchEvent(ev)
}
if (ev.action == MotionEvent.ACTION_DOWN && overScroller.computeScrollOffset()) {
overScroller.forceFinished(true)
}
gestureDetector.onTouchEvent(ev)
scaleDetector.onTouchEvent(ev)
// Offset event to inside the child view
if (scale < 1 && !targetHitRect.contains(ev.x.toInt(), ev.y.toInt())) {
ev.offsetLocation(halfWidth - ev.x + targetHitRect.width() / 3, 0f)
}
// Send action cancel to avoid recycler jump when scale end
if (scaleDetector.isInProgress) {
ev.action = MotionEvent.ACTION_CANCEL
}
return super.dispatchTouchEvent(ev)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
halfWidth = measuredWidth / 2f
halfHeight = measuredHeight / 2f
}
private fun invalidateTarget() {
adjustBounds()
targetChild.run {
scaleX = scale
scaleY = scale
translationX = transX
translationY = transY
}
val newHeight = if (scale < 1f) (height / scale).toInt() else height
if (newHeight != targetChild.height) {
targetChild.layoutParams.height = newHeight
targetChild.requestLayout()
}
if (scale < 1) {
targetChild.getHitRect(targetHitRect)
targetChild.scrollBy(0, pendingScroll)
pendingScroll = 0
}
}
private fun syncMatrixValues() {
transformMatrix.getValues(matrixValues)
}
private fun adjustBounds() {
syncMatrixValues()
val dx = when {
transX < translateBounds.left -> translateBounds.left - transX
transX > translateBounds.right -> translateBounds.right - transX
else -> 0f
}
val dy = when {
transY < translateBounds.top -> translateBounds.top - transY
transY > translateBounds.bottom -> translateBounds.bottom - transY
else -> 0f
}
pendingScroll = dy.toInt()
transformMatrix.postTranslate(dx, dy)
syncMatrixValues()
}
private fun scaleChild(newScale: Float, focusX: Float, focusY: Float) {
val factor = newScale / scale
if (newScale > 1) {
translateBounds.set(
halfWidth * (1 - newScale),
halfHeight * (1 - newScale),
halfWidth * (newScale - 1),
halfHeight * (newScale - 1),
)
} else {
translateBounds.set(
0f,
halfHeight - halfHeight / newScale,
0f,
halfHeight - halfHeight / newScale,
)
}
transformMatrix.postScale(factor, factor, focusX, focusY)
invalidateTarget()
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
val newScale = (scale * detector.scaleFactor).coerceIn(MIN_SCALE, MAX_SCALE)
scaleChild(newScale, detector.focusX, detector.focusY)
return true
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean = true
override fun onScaleEnd(p0: ScaleGestureDetector) {
pendingScroll = 0
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener(), Runnable {
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
if (scale <= 1f) return false
transformMatrix.postTranslate(-distanceX, -distanceY)
invalidateTarget()
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
val newScale = if (scale != 1f) 1f else MAX_SCALE * 0.8f
ObjectAnimator.ofFloat(scale, newScale).run {
interpolator = AccelerateDecelerateInterpolator()
duration = 300
addUpdateListener {
scaleChild(it.animatedValue as Float, e.x, e.y)
}
start()
}
return true
}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
if (scale <= 1) return false
overScroller.fling(
transX.toInt(),
transY.toInt(),
velocityX.toInt(),
velocityY.toInt(),
translateBounds.left.toInt(),
translateBounds.right.toInt(),
translateBounds.top.toInt(),
translateBounds.bottom.toInt(),
)
postOnAnimation(this)
return true
}
override fun run() {
if (overScroller.computeScrollOffset()) {
transformMatrix.postTranslate(overScroller.currX - transX, overScroller.currY - transY)
invalidateTarget()
postOnAnimation(this)
}
}
}
}

View File

@@ -163,7 +163,7 @@ class RemoteListViewModel @AssistedInject constructor(
}
private fun createEmptyState(canResetFilter: Boolean) = EmptyState(
icon = R.drawable.ic_empty_search,
icon = R.drawable.ic_empty_common,
textPrimary = R.string.nothing_found,
textSecondary = 0,
actionStringRes = if (canResetFilter) R.string.reset_filter else 0,

View File

@@ -48,7 +48,7 @@ class SearchViewModel @AssistedInject constructor(
list == null -> listOf(LoadingState)
list.isEmpty() -> listOf(
EmptyState(
icon = R.drawable.ic_empty_search,
icon = R.drawable.ic_empty_common,
textPrimary = R.string.nothing_found,
textSecondary = R.string.text_search_holder_secondary,
actionStringRes = 0,

View File

@@ -48,7 +48,7 @@ class MultiSearchViewModel @AssistedInject constructor(
loading -> LoadingState
error != null -> error.toErrorState(canRetry = true)
else -> EmptyState(
icon = R.drawable.ic_empty_search,
icon = R.drawable.ic_empty_common,
textPrimary = R.string.nothing_found,
textSecondary = R.string.text_search_holder_secondary,
actionStringRes = 0,

View File

@@ -11,8 +11,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.DialogOnboardBinding
import org.koitharu.kotatsu.settings.onboard.adapter.SourceLocaleListener
import org.koitharu.kotatsu.settings.onboard.adapter.SourceLocalesAdapter
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
import org.koitharu.kotatsu.utils.ext.showAllowStateLoss
@@ -21,8 +21,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
@AndroidEntryPoint
class OnboardDialogFragment :
AlertDialogFragment<DialogOnboardBinding>(),
OnListItemClickListener<SourceLocale>,
DialogInterface.OnClickListener {
DialogInterface.OnClickListener, SourceLocaleListener {
private val viewModel by viewModels<OnboardViewModel>()
private var isWelcome: Boolean = false
@@ -63,8 +62,8 @@ class OnboardDialogFragment :
}
}
override fun onItemClick(item: SourceLocale, view: View) {
viewModel.setItemChecked(item.key, !item.isChecked)
override fun onItemCheckedChanged(item: SourceLocale, isChecked: Boolean) {
viewModel.setItemChecked(item.key, isChecked)
}
override fun onClick(dialog: DialogInterface?, which: Int) {

View File

@@ -1,11 +1,8 @@
package org.koitharu.kotatsu.settings.onboard
import androidx.collection.ArraySet
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.MutableLiveData
import dagger.hilt.android.lifecycle.HiltViewModel
import java.util.*
import javax.inject.Inject
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings
@@ -15,6 +12,8 @@ import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
import org.koitharu.kotatsu.utils.ext.map
import org.koitharu.kotatsu.utils.ext.mapToSet
import java.util.Locale
import javax.inject.Inject
@HiltViewModel
class OnboardViewModel @Inject constructor(
@@ -23,9 +22,9 @@ class OnboardViewModel @Inject constructor(
private val allSources = settings.remoteMangaSources
private val locales = allSources.mapTo(ArraySet()) { it.locale }
private val locales = allSources.groupBy { it.locale }
private val selectedLocales = locales.toMutableSet()
private val selectedLocales = locales.keys.toMutableSet()
val list = MutableLiveData<List<SourceLocale>?>()
@@ -64,13 +63,14 @@ class OnboardViewModel @Inject constructor(
}
private fun rebuildList() {
list.value = locales.map { key ->
list.value = locales.map { (key, srcs) ->
val locale = if (key != null) {
Locale(key)
} else null
SourceLocale(
key = key,
title = locale?.getDisplayLanguage(locale)?.toTitleCase(locale),
summary = srcs.joinToString { it.title },
isChecked = key in selectedLocales,
)
}.sortedWith(SourceLocaleComparator())
@@ -87,11 +87,12 @@ class OnboardViewModel @Inject constructor(
a?.key == null -> 1
b?.key == null -> -1
else -> {
val index = deviceLocales.indexOf(a.key)
if (index == -1) {
val indexA = deviceLocales.indexOf(a.key)
val indexB = deviceLocales.indexOf(b.key)
if (indexA == -1 && indexB == -1) {
compareValues(a.title, b.title)
} else {
-2 - index
-2 - (indexA - indexB)
}
}
}

View File

@@ -2,22 +2,23 @@ package org.koitharu.kotatsu.settings.onboard.adapter
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemSourceLocaleBinding
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
import org.koitharu.kotatsu.utils.ext.textAndVisible
fun sourceLocaleAD(
clickListener: OnListItemClickListener<SourceLocale>
listener: SourceLocaleListener,
) = adapterDelegateViewBinding<SourceLocale, SourceLocale, ItemSourceLocaleBinding>(
{ inflater, parent -> ItemSourceLocaleBinding.inflate(inflater, parent, false) }
{ inflater, parent -> ItemSourceLocaleBinding.inflate(inflater, parent, false) },
) {
binding.root.setOnClickListener {
clickListener.onItemClick(item, it)
binding.switchToggle.setOnCheckedChangeListener { _, isChecked ->
listener.onItemCheckedChanged(item, isChecked)
}
bind {
binding.root.text = item.title ?: getString(R.string.other)
binding.root.isChecked = item.isChecked
binding.textViewTitle.text = item.title ?: getString(R.string.different_languages)
binding.textViewDescription.textAndVisible = item.summary
binding.switchToggle.isChecked = item.isChecked
}
}
}

View File

@@ -0,0 +1,8 @@
package org.koitharu.kotatsu.settings.onboard.adapter
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
interface SourceLocaleListener {
fun onItemCheckedChanged(item: SourceLocale, isChecked: Boolean)
}

View File

@@ -2,15 +2,14 @@ package org.koitharu.kotatsu.settings.onboard.adapter
import androidx.recyclerview.widget.DiffUtil
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.settings.onboard.model.SourceLocale
class SourceLocalesAdapter(
clickListener: OnListItemClickListener<SourceLocale>,
listener: SourceLocaleListener,
) : AsyncListDifferDelegationAdapter<SourceLocale>(DiffCallback()) {
init {
delegatesManager.addDelegate(sourceLocaleAD(clickListener))
delegatesManager.addDelegate(sourceLocaleAD(listener))
}
private class DiffCallback : DiffUtil.ItemCallback<SourceLocale>() {
@@ -25,4 +24,4 @@ class SourceLocalesAdapter(
newItem: SourceLocale,
): Boolean = oldItem == newItem
}
}
}

View File

@@ -1,10 +1,11 @@
package org.koitharu.kotatsu.settings.onboard.model
import java.util.*
import java.util.Locale
data class SourceLocale(
val key: String?,
val title: String?,
val summary: String?,
val isChecked: Boolean,
) : Comparable<SourceLocale> {

View File

@@ -1,19 +1,44 @@
package org.koitharu.kotatsu.shelf.domain
import javax.inject.Inject
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
import org.koitharu.kotatsu.favourites.data.toFavouriteCategory
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import javax.inject.Inject
class ShelfRepository @Inject constructor(
private val localMangaRepository: LocalMangaRepository,
private val historyRepository: HistoryRepository,
private val db: MangaDatabase,
) {
fun observeLocalManga(sortOrder: SortOrder): Flow<List<Manga>> {
return flow {
emit(null)
emitAll(localMangaRepository.watchReadableDirs())
}.mapLatest {
localMangaRepository.getList(0, null, sortOrder)
}
}
fun observeFavourites(): Flow<Map<FavouriteCategory, List<Manga>>> {
return db.favouriteCategoriesDao.observeAll()
.flatMapLatest { categories ->
@@ -26,6 +51,23 @@ class ShelfRepository @Inject constructor(
}
}
suspend fun deleteLocalManga(ids: Set<Long>) {
val list = localMangaRepository.getList(0, null, null)
.filter { x -> x.id in ids }
coroutineScope {
list.map { manga ->
async {
val original = localMangaRepository.getRemoteManga(manga)
if (localMangaRepository.delete(manga)) {
runCatchingCancellable {
historyRepository.deleteOrSwap(manga, original)
}
}
}
}.awaitAll()
}
}
private fun observeCategoriesContent(
categories: List<FavouriteCategoryEntity>,
) = combine<Pair<FavouriteCategory, List<Manga>>, Map<FavouriteCategory, List<Manga>>>(

View File

@@ -1,6 +1,9 @@
package org.koitharu.kotatsu.shelf.ui
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -11,7 +14,6 @@ import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.reverseAsync
import org.koitharu.kotatsu.base.ui.BaseFragment
@@ -23,15 +25,19 @@ import org.koitharu.kotatsu.databinding.FragmentShelfBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.favourites.ui.FavouritesActivity
import org.koitharu.kotatsu.history.ui.HistoryActivity
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfAdapter
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfListEventListener
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.search.ui.MangaListActivity
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfAdapter
import org.koitharu.kotatsu.shelf.ui.adapter.ShelfListEventListener
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.tracker.ui.updates.UpdatesActivity
import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import javax.inject.Inject
@AndroidEntryPoint
class ShelfFragment :
@@ -102,13 +108,22 @@ class ShelfFragment :
val intent = when (section) {
is ShelfSectionModel.History -> HistoryActivity.newIntent(view.context)
is ShelfSectionModel.Favourites -> FavouritesActivity.newIntent(view.context, section.category)
is ShelfSectionModel.Updated -> UpdatesActivity.newIntent(view.context)
is ShelfSectionModel.Local -> MangaListActivity.newIntent(view.context, MangaSource.LOCAL)
}
startActivity(intent)
}
override fun onRetryClick(error: Throwable) = Unit
override fun onEmptyActionClick() = Unit
override fun onEmptyActionClick() {
val action = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Settings.Panel.ACTION_INTERNET_CONNECTIVITY
} else {
Settings.ACTION_WIRELESS_SETTINGS
}
startActivity(Intent(action))
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.recyclerView.updatePadding(

View File

@@ -6,15 +6,16 @@ import android.view.MenuItem
import androidx.appcompat.view.ActionMode
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.decor.AbstractSelectionItemDecoration
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.favourites.ui.categories.select.FavouriteCategoriesBottomSheet
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.MangaSelectionDecoration
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.flattenTo
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.invalidateNestedItemDecorations
@@ -41,8 +42,10 @@ class ShelfSelectionCallback(
mode: ActionMode,
menu: Menu,
): Boolean {
menu.findItem(R.id.action_remove).isVisible =
controller.peekCheckedIds().count { (_, v) -> v.isNotEmpty() } == 1
val checkedIds = controller.peekCheckedIds().entries
val singleKey = checkedIds.singleOrNull { (_, ids) -> ids.isNotEmpty() }?.key
menu.findItem(R.id.action_remove)?.isVisible = singleKey != null && singleKey !is ShelfSectionModel.Updated
menu.findItem(R.id.action_save)?.isVisible = singleKey !is ShelfSectionModel.Local
return super.onPrepareActionMode(controller, mode, menu)
}
@@ -57,25 +60,34 @@ class ShelfSelectionCallback(
mode.finish()
true
}
R.id.action_favourite -> {
FavouriteCategoriesBottomSheet.show(fragmentManager, collectSelectedItems(controller))
mode.finish()
true
}
R.id.action_save -> {
DownloadService.confirmAndStart(context, collectSelectedItems(controller))
mode.finish()
true
}
R.id.action_remove -> {
val (group, ids) = controller.snapshot().entries.singleOrNull { it.value.isNotEmpty() } ?: return false
when (group) {
is ShelfSectionModel.Favourites -> viewModel.removeFromFavourites(group.category, ids)
is ShelfSectionModel.History -> viewModel.removeFromHistory(ids)
is ShelfSectionModel.Updated -> return false
is ShelfSectionModel.Local -> {
showDeletionConfirm(ids, mode)
return true
}
}
mode.finish()
true
}
else -> false
}
}
@@ -108,4 +120,19 @@ class ShelfSelectionCallback(
}
return viewModel.getManga(snapshot.values.flattenTo(HashSet()))
}
private fun showDeletionConfirm(ids: Set<Long>, mode: ActionMode) {
if (ids.isEmpty()) {
return
}
MaterialAlertDialogBuilder(context ?: return)
.setTitle(R.string.delete_manga)
.setMessage(context.getString(R.string.text_delete_local_manga_batch))
.setPositiveButton(R.string.delete) { _, _ ->
viewModel.deleteLocal(ids)
mode.finish()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}

View File

@@ -4,53 +4,63 @@ import androidx.collection.ArraySet
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import java.util.*
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.base.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.os.NetworkStateObserver
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.MangaWithHistory
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.model.EmptyHint
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toGridModel
import org.koitharu.kotatsu.list.ui.model.toUi
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.shelf.domain.ShelfRepository
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.domain.ListExtraProvider
import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.daysDiff
private const val HISTORY_MAX_SEGMENTS = 2
import javax.inject.Inject
@HiltViewModel
class ShelfViewModel @Inject constructor(
repository: ShelfRepository,
private val repository: ShelfRepository,
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val trackingRepository: TrackingRepository,
private val settings: AppSettings,
private val networkStateObserver: NetworkStateObserver,
) : BaseViewModel(), ListExtraProvider {
val onActionDone = SingleLiveEvent<ReversibleAction>()
val content: LiveData<List<ListModel>> = combine(
networkStateObserver,
historyRepository.observeAllWithHistory(),
repository.observeLocalManga(SortOrder.UPDATED),
repository.observeFavourites(),
) { history, favourites ->
mapList(history, favourites)
}.catch { e ->
emit(listOf(e.toErrorState(canRetry = false)))
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
trackingRepository.observeUpdatedManga(),
) { isConnected, history, local, favourites, updated ->
mapList(history, favourites, updated, local, isConnected)
}.debounce(500)
.catch { e ->
emit(listOf(e.toErrorState(canRetry = false)))
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
override suspend fun getCounter(mangaId: Long): Int {
return trackingRepository.getNewChaptersCount(mangaId)
@@ -84,6 +94,13 @@ class ShelfViewModel @Inject constructor(
}
}
fun deleteLocal(ids: Set<Long>) {
launchLoadingJob(Dispatchers.Default) {
repository.deleteLocalManga(ids)
onActionDone.postCall(ReversibleAction(R.string.removal_completed, null))
}
}
fun clearHistory(minDate: Long) {
launchJob(Dispatchers.Default) {
val stringRes = if (minDate <= 0) {
@@ -119,13 +136,38 @@ class ShelfViewModel @Inject constructor(
private suspend fun mapList(
history: List<MangaWithHistory>,
favourites: Map<FavouriteCategory, List<Manga>>,
updated: Map<Manga, Int>,
local: List<Manga>,
isNetworkAvailable: Boolean,
): List<ListModel> {
val result = ArrayList<ListModel>(favourites.keys.size + 1)
if (history.isNotEmpty()) {
mapHistory(result, history)
}
if (favourites.isNotEmpty()) {
mapFavourites(result, favourites)
val result = ArrayList<ListModel>(favourites.keys.size + 3)
if (isNetworkAvailable) {
if (history.isNotEmpty()) {
mapHistory(result, history)
}
if (local.isNotEmpty()) {
mapLocal(result, local)
}
if (updated.isNotEmpty()) {
mapUpdated(result, updated)
}
if (favourites.isNotEmpty()) {
mapFavourites(result, favourites)
}
} else {
result += EmptyHint(
icon = R.drawable.ic_empty_common,
textPrimary = R.string.network_unavailable,
textSecondary = R.string.network_unavailable_hint,
actionStringRes = R.string.manage,
)
val offlineHistory = history.filter { it.manga.source == MangaSource.LOCAL }
if (offlineHistory.isNotEmpty()) {
mapHistory(result, offlineHistory)
}
if (local.isNotEmpty()) {
mapLocal(result, local)
}
}
if (result.isEmpty()) {
result += EmptyState(
@@ -134,8 +176,12 @@ class ShelfViewModel @Inject constructor(
textSecondary = R.string.text_shelf_holder_secondary,
actionStringRes = 0,
)
} else {
val one = result.singleOrNull()
if (one is EmptyHint) {
result[0] = one.toState()
}
}
result.trimToSize()
return result
}
@@ -144,23 +190,38 @@ class ShelfViewModel @Inject constructor(
list: List<MangaWithHistory>,
) {
val showPercent = settings.isReadingIndicatorsEnabled
val groups = list.groupByTo(LinkedHashMap()) { timeAgo(it.history.updatedAt) }
while (groups.size > HISTORY_MAX_SEGMENTS) {
val lastKey = groups.keys.last()
val subList = groups.remove(lastKey) ?: continue
groups[groups.keys.last()]?.addAll(subList)
}
for ((timeAgo, subList) in groups) {
destination += ShelfSectionModel.History(
items = subList.map { (manga, history) ->
val counter = trackingRepository.getNewChaptersCount(manga.id)
val percent = if (showPercent) history.percent else PROGRESS_NONE
manga.toGridModel(counter, percent)
},
timeAgo = timeAgo,
showAllButtonText = R.string.show_all,
)
}
destination += ShelfSectionModel.History(
items = list.map { (manga, history) ->
val counter = trackingRepository.getNewChaptersCount(manga.id)
val percent = if (showPercent) history.percent else PROGRESS_NONE
manga.toGridModel(counter, percent)
},
showAllButtonText = R.string.show_all,
)
}
private suspend fun mapUpdated(
destination: MutableList<in ShelfSectionModel.Updated>,
updated: Map<Manga, Int>,
) {
val showPercent = settings.isReadingIndicatorsEnabled
destination += ShelfSectionModel.Updated(
items = updated.map { (manga, counter) ->
val percent = if (showPercent) getProgress(manga.id) else PROGRESS_NONE
manga.toGridModel(counter, percent)
},
showAllButtonText = R.string.show_all,
)
}
private suspend fun mapLocal(
destination: MutableList<in ShelfSectionModel.Local>,
local: List<Manga>,
) {
destination += ShelfSectionModel.Local(
items = local.toUi(ListMode.GRID, this),
showAllButtonText = R.string.show_all,
)
}
private suspend fun mapFavourites(
@@ -177,14 +238,4 @@ class ShelfViewModel @Inject constructor(
}
}
}
private fun timeAgo(date: Date): DateTimeAgo {
val diffDays = -date.daysDiff(System.currentTimeMillis())
return when {
diffDays < 1 -> DateTimeAgo.Today
diffDays == 1 -> DateTimeAgo.Yesterday
diffDays <= 3 -> DateTimeAgo.DaysAgo(diffDays)
else -> DateTimeAgo.LongAgo
}
}
}

View File

@@ -6,16 +6,17 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import kotlin.jvm.internal.Intrinsics
import org.koitharu.kotatsu.base.ui.list.SectionedSelectionController
import org.koitharu.kotatsu.base.ui.list.fastscroll.FastScroller
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import org.koitharu.kotatsu.list.ui.ItemSizeResolver
import org.koitharu.kotatsu.list.ui.adapter.emptyHintAD
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.shelf.ui.model.ShelfSectionModel
import kotlin.jvm.internal.Intrinsics
class ShelfAdapter(
lifecycleOwner: LifecycleOwner,
@@ -40,6 +41,7 @@ class ShelfAdapter(
)
.addDelegate(loadingStateAD())
.addDelegate(loadingFooterAD())
.addDelegate(emptyHintAD(listener))
.addDelegate(emptyStateListAD(coil, listener))
.addDelegate(errorStateListAD(listener))
}
@@ -56,6 +58,7 @@ class ShelfAdapter(
oldItem is ShelfSectionModel && newItem is ShelfSectionModel -> {
oldItem.key == newItem.key
}
else -> oldItem.javaClass == newItem.javaClass
}
}

View File

@@ -4,30 +4,29 @@ import android.content.res.Resources
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaItemModel
sealed class ShelfSectionModel(
val items: List<MangaItemModel>,
@StringRes val showAllButtonText: Int,
) : ListModel {
sealed interface ShelfSectionModel : ListModel {
abstract val key: Any
abstract fun getTitle(resources: Resources): CharSequence
val items: List<MangaItemModel>
@get:StringRes
val showAllButtonText: Int
val key: String
fun getTitle(resources: Resources): CharSequence
override fun toString(): String
class History(
items: List<MangaItemModel>,
val timeAgo: DateTimeAgo?,
showAllButtonText: Int,
) : ShelfSectionModel(items, showAllButtonText) {
override val items: List<MangaItemModel>,
override val showAllButtonText: Int,
) : ShelfSectionModel {
override val key: Any
get() = timeAgo?.javaClass ?: this::class.java
override val key = "history"
override fun getTitle(resources: Resources): CharSequence {
return timeAgo?.format(resources) ?: resources.getString(R.string.history)
}
override fun getTitle(resources: Resources) = resources.getString(R.string.history)
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -35,7 +34,6 @@ sealed class ShelfSectionModel(
other as History
if (timeAgo != other.timeAgo) return false
if (showAllButtonText != other.showAllButtonText) return false
if (items != other.items) return false
@@ -44,28 +42,22 @@ sealed class ShelfSectionModel(
override fun hashCode(): Int {
var result = items.hashCode()
result = 31 * result + (timeAgo?.hashCode() ?: 0)
result = 31 * result + showAllButtonText.hashCode()
return result
}
override fun toString(): String {
return "hist_$timeAgo"
}
override fun toString(): String = key
}
class Favourites(
items: List<MangaItemModel>,
override val items: List<MangaItemModel>,
val category: FavouriteCategory,
showAllButtonText: Int,
) : ShelfSectionModel(items, showAllButtonText) {
override val showAllButtonText: Int,
) : ShelfSectionModel {
override val key: Any
get() = category.id
override val key = "fav_${category.id}"
override fun getTitle(resources: Resources): CharSequence {
return category.title
}
override fun getTitle(resources: Resources) = category.title
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -87,8 +79,66 @@ sealed class ShelfSectionModel(
return result
}
override fun toString(): String {
return "fav_${category.id}"
override fun toString(): String = key
}
class Updated(
override val items: List<MangaItemModel>,
override val showAllButtonText: Int,
) : ShelfSectionModel {
override val key = "upd"
override fun getTitle(resources: Resources) = resources.getString(R.string.updated)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Updated
if (items != other.items) return false
if (showAllButtonText != other.showAllButtonText) return false
return true
}
override fun hashCode(): Int {
var result = items.hashCode()
result = 31 * result + showAllButtonText
return result
}
override fun toString(): String = key
}
class Local(
override val items: List<MangaItemModel>,
override val showAllButtonText: Int,
) : ShelfSectionModel {
override val key = "local"
override fun getTitle(resources: Resources) = resources.getString(R.string.local_storage)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Local
if (items != other.items) return false
if (showAllButtonText != other.showAllButtonText) return false
return true
}
override fun hashCode(): Int {
var result = items.hashCode()
result = 31 * result + showAllButtonText
return result
}
override fun toString(): String = key
}
}

View File

@@ -31,7 +31,7 @@ class SuggestionsViewModel @Inject constructor(
when {
list.isEmpty() -> listOf(
EmptyState(
icon = R.drawable.ic_empty_suggestions,
icon = R.drawable.ic_empty_common,
textPrimary = R.string.nothing_found,
textSecondary = R.string.text_suggestion_holder,
actionStringRes = 0,

View File

@@ -1,7 +1,14 @@
package org.koitharu.kotatsu.tracker.data
import androidx.room.*
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.MapInfo
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.db.entity.MangaWithTags
@Dao
abstract class TracksDao {
@@ -28,9 +35,17 @@ abstract class TracksDao {
@Query("SELECT chapters_new FROM tracks WHERE manga_id = :mangaId")
abstract fun observeNewChapters(mangaId: Long): Flow<Int?>
@Transaction
@MapInfo(valueColumn = "chapters_new")
@Query("SELECT manga.*, chapters_new FROM tracks LEFT JOIN manga ON manga.manga_id = tracks.manga_id WHERE chapters_new > 0 ORDER BY chapters_new DESC")
abstract fun observeUpdatedManga(): Flow<Map<MangaWithTags, Int>>
@Query("DELETE FROM tracks")
abstract suspend fun clear()
@Query("UPDATE tracks SET chapters_new = 0")
abstract suspend fun clearCounters()
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun insert(entity: TrackEntity): Long

View File

@@ -2,10 +2,9 @@ package org.koitharu.kotatsu.tracker.domain
import androidx.annotation.VisibleForTesting
import androidx.room.withTransaction
import java.util.*
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase
@@ -22,6 +21,8 @@ import org.koitharu.kotatsu.tracker.data.toTrackingLogItem
import org.koitharu.kotatsu.tracker.domain.model.MangaTracking
import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
import java.util.Date
import javax.inject.Inject
private const val NO_ID = 0L
@@ -41,6 +42,12 @@ class TrackingRepository @Inject constructor(
return db.tracksDao.observeNewChapters().map { list -> list.count { it > 0 } }
}
fun observeUpdatedManga(): Flow<Map<Manga, Int>> {
return db.tracksDao.observeUpdatedManga()
.map { x -> x.mapKeys { it.key.toManga() } }
.distinctUntilChanged()
}
suspend fun getTracks(mangaList: Collection<Manga>): List<MangaTracking> {
val ids = mangaList.mapToSet { it.id }
val tracks = db.tracksDao.findAll(ids).groupBy { it.mangaId }
@@ -91,6 +98,8 @@ class TrackingRepository @Inject constructor(
suspend fun clearLogs() = db.trackLogsDao.clear()
suspend fun clearCounters() = db.tracksDao.clearCounters()
suspend fun gc() {
db.withTransaction {
db.tracksDao.gc()

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.tracker.ui
package org.koitharu.kotatsu.tracker.ui.feed
import android.os.Bundle
import android.view.LayoutInflater
@@ -10,7 +10,6 @@ import androidx.fragment.app.viewModels
import coil.ImageLoader
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseFragment
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
@@ -23,11 +22,12 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.tracker.ui.adapter.FeedAdapter
import org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter
import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.ext.addMenuProvider
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getThemeColor
import javax.inject.Inject
@AndroidEntryPoint
class FeedFragment :

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.tracker.ui
package org.koitharu.kotatsu.tracker.ui.feed
import android.content.Context
import android.view.Menu
@@ -6,9 +6,9 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.view.MenuProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.dialog.CheckBoxAlertDialog
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.tracker.work.TrackWorker
@@ -37,21 +37,26 @@ class FeedMenuProvider(
snackbar.show()
true
}
R.id.action_clear_feed -> {
MaterialAlertDialogBuilder(context)
CheckBoxAlertDialog.Builder(context)
.setTitle(R.string.clear_updates_feed)
.setMessage(R.string.text_clear_updates_feed_prompt)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.clear) { _, _ ->
viewModel.clearFeed()
}.show()
.setCheckBoxChecked(true)
.setCheckBoxText(R.string.clear_new_chapters_counters)
.setPositiveButton(R.string.clear) { _, isChecked ->
viewModel.clearFeed(isChecked)
}.create().show()
true
}
R.id.action_settings -> {
val intent = SettingsActivity.newTrackerSettingsIntent(context)
context.startActivity(intent)
true
}
else -> false
}
}

View File

@@ -1,11 +1,7 @@
package org.koitharu.kotatsu.tracker.ui
package org.koitharu.kotatsu.tracker.ui.feed
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import java.util.*
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
@@ -17,10 +13,14 @@ import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem
import org.koitharu.kotatsu.tracker.ui.model.toFeedItem
import org.koitharu.kotatsu.tracker.ui.feed.model.toFeedItem
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.daysDiff
import java.util.Date
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
private const val PAGE_SIZE = 20
@@ -50,9 +50,12 @@ class FeedViewModel @Inject constructor(
}
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
fun clearFeed() {
fun clearFeed(clearCounters: Boolean) {
launchLoadingJob(Dispatchers.Default) {
repository.clearLogs()
if (clearCounters) {
repository.clearCounters()
}
onFeedCleared.postCall(Unit)
}
}

View File

@@ -1,14 +1,20 @@
package org.koitharu.kotatsu.tracker.ui.adapter
package org.koitharu.kotatsu.tracker.ui.feed.adapter
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil
import coil.ImageLoader
import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter
import kotlin.jvm.internal.Intrinsics
import org.koitharu.kotatsu.core.ui.DateTimeAgo
import org.koitharu.kotatsu.list.ui.adapter.*
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
import org.koitharu.kotatsu.list.ui.adapter.errorFooterAD
import org.koitharu.kotatsu.list.ui.adapter.errorStateListAD
import org.koitharu.kotatsu.list.ui.adapter.loadingFooterAD
import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD
import org.koitharu.kotatsu.list.ui.adapter.relatedDateItemAD
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
import kotlin.jvm.internal.Intrinsics
class FeedAdapter(
coil: ImageLoader,
@@ -33,9 +39,11 @@ class FeedAdapter(
oldItem is FeedItem && newItem is FeedItem -> {
oldItem.id == newItem.id
}
oldItem is DateTimeAgo && newItem is DateTimeAgo -> {
oldItem == newItem
}
else -> oldItem.javaClass == newItem.javaClass
}

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.tracker.ui.adapter
package org.koitharu.kotatsu.tracker.ui.feed.adapter
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemFeedBinding
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.tracker.ui.model.FeedItem
import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.isBold

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.tracker.ui.model
package org.koitharu.kotatsu.tracker.ui.feed.model
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga

View File

@@ -1,4 +1,4 @@
package org.koitharu.kotatsu.tracker.ui.model
package org.koitharu.kotatsu.tracker.ui.feed.model
import org.koitharu.kotatsu.tracker.domain.model.TrackingLogItem

View File

@@ -0,0 +1,49 @@
package org.koitharu.kotatsu.tracker.ui.updates
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import com.google.android.material.appbar.AppBarLayout
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
@AndroidEntryPoint
class UpdatesActivity :
BaseActivity<ActivityContainerBinding>(),
AppBarOwner {
override val appBar: AppBarLayout
get() = binding.appbar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityContainerBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
setReorderingAllowed(true)
val fragment = UpdatesFragment.newInstance()
replace(R.id.container, fragment)
}
}
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.root.updatePadding(
left = insets.left,
right = insets.right,
)
}
companion object {
fun newIntent(context: Context) = Intent(context, UpdatesActivity::class.java)
}
}

View File

@@ -0,0 +1,19 @@
package org.koitharu.kotatsu.tracker.ui.updates
import androidx.fragment.app.viewModels
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.list.ui.MangaListFragment
@AndroidEntryPoint
class UpdatesFragment : MangaListFragment() {
override val viewModel by viewModels<UpdatesViewModel>()
override val isSwipeRefreshEnabled = false
override fun onScrolledToEnd() = Unit
companion object {
fun newInstance() = UpdatesFragment()
}
}

View File

@@ -0,0 +1,77 @@
package org.koitharu.kotatsu.tracker.ui.updates
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.list.ui.model.toGridModel
import org.koitharu.kotatsu.list.ui.model.toListDetailedModel
import org.koitharu.kotatsu.list.ui.model.toListModel
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.asFlowLiveData
import org.koitharu.kotatsu.utils.ext.onFirst
import javax.inject.Inject
@HiltViewModel
class UpdatesViewModel @Inject constructor(
private val repository: TrackingRepository,
private val settings: AppSettings,
private val historyRepository: HistoryRepository,
) : MangaListViewModel(settings) {
override val content = combine(
repository.observeUpdatedManga(),
createListModeFlow(),
) { mangaMap, mode ->
when {
mangaMap.isEmpty() -> listOf(
EmptyState(
icon = R.drawable.ic_empty_history,
textPrimary = R.string.text_history_holder_primary,
textSecondary = R.string.text_history_holder_secondary,
actionStringRes = 0,
),
)
else -> mapList(mangaMap, mode)
}
}.onStart {
loadingCounter.increment()
}.onFirst {
loadingCounter.decrement()
}.catch {
emit(listOf(it.toErrorState(canRetry = false)))
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, listOf(LoadingState))
override fun onRefresh() = Unit
override fun onRetry() = Unit
private suspend fun mapList(
mangaMap: Map<Manga, Int>,
mode: ListMode,
): List<ListModel> {
val showPercent = settings.isReadingIndicatorsEnabled
return mangaMap.map { (manga, counter) ->
val percent = if (showPercent) historyRepository.getProgress(manga.id) else PROGRESS_NONE
when (mode) {
ListMode.LIST -> manga.toListModel(counter, percent)
ListMode.DETAILED_LIST -> manga.toListDetailedModel(counter, percent)
ListMode.GRID -> manga.toGridModel(counter, percent)
}
}
}
}

View File

@@ -0,0 +1,46 @@
package org.koitharu.kotatsu.utils
import android.app.Activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.DefaultActivityLifecycleCallbacks
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.utils.ext.getThemeColor
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class IncognitoModeIndicator @Inject constructor(
private val settings: AppSettings,
) : DefaultActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (activity !is AppCompatActivity) {
return
}
settings.observeAsFlow(
key = AppSettings.KEY_INCOGNITO_MODE,
valueProducer = { isIncognitoModeEnabled },
).flowOn(Dispatchers.IO)
.flowWithLifecycle(activity.lifecycle)
.onEach { updateStatusBar(activity, it) }
.launchIn(activity.lifecycleScope)
}
private fun updateStatusBar(activity: AppCompatActivity, isIncognitoModeEnabled: Boolean) {
activity.window.statusBarColor = if (isIncognitoModeEnabled) {
ContextCompat.getColor(activity, R.color.status_bar_incognito)
} else {
activity.getThemeColor(android.R.attr.statusBarColor)
}
}
}

View File

@@ -44,6 +44,14 @@ val Context.activityManager: ActivityManager?
val Context.connectivityManager: ConnectivityManager
get() = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val ConnectivityManager.isNetworkAvailable: Boolean
get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
activeNetwork != null
} else {
@Suppress("DEPRECATION")
activeNetworkInfo?.isConnectedOrConnecting == true
}
fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this)
suspend fun CoroutineWorker.trySetForeground(): Boolean = runCatchingCancellable {

View File

@@ -8,6 +8,7 @@ import okio.FileNotFoundException
import okio.IOException
import org.acra.ktx.sendWithAcra
import org.json.JSONException
import org.jsoup.HttpStatusException
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CaughtException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
@@ -45,6 +46,12 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is WrongPasswordException -> resources.getString(R.string.wrong_password)
is NotFoundException -> resources.getString(R.string.not_found_404)
is HttpStatusException -> when (statusCode) {
in 500..599 -> resources.getString(R.string.server_error, statusCode)
else -> localizedMessage
}
is IOException -> getDisplayMessage(message, resources) ?: localizedMessage
else -> localizedMessage
} ?: resources.getString(R.string.error_occurred)

View File

@@ -127,7 +127,8 @@ fun <T> RecyclerView.ViewHolder.getItem(clazz: Class<T>): T? {
fun Slider.setValueRounded(newValue: Float) {
val step = stepSize
value = (newValue / step).roundToInt() * step
val roundedValue = (newValue / step).roundToInt() * step
value = roundedValue.coerceIn(valueFrom, valueTo)
}
val RecyclerView.isScrolledToTop: Boolean

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.4" android:color="?attr/colorPrimaryInverse" />
</selector>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.2" android:color="?colorPrimary" />
</selector>
<item android:alpha="0.2" android:color="?attr/colorPrimaryInverse" />
</selector>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.7" android:color="?attr/colorSecondaryContainer" />
</selector>
</selector>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="0.4" android:color="@color/kotatsu_inversePrimary" />
</selector>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- https://stackoverflow.com/questions/54685474/theme-attributes-in-color-selector-for-api-22 -->
<item android:alpha="0.2" android:color="@color/kotatsu_primary" />
</selector>
<item android:alpha="0.2" android:color="@color/kotatsu_inversePrimary" />
</selector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,71 +1,53 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="682.67"
android:viewportHeight="682.67">
android:width="200dp"
android:height="200dp"
android:viewportWidth="200"
android:viewportHeight="200">
<path
android:fillColor="#ededed"
android:pathData="m462.38,677c-0.95,-9.58 11.4,-56.66 18.63,-71 8.03,-15.92 33.35,-48.08 35.88,-45.56 0.5,0.5 33.92,111.28 35.96,119.22l0.77,3L508.28,682.67 462.95,682.67ZM156,615.11c-14.7,-2.58 -20.07,-6.87 -22.71,-18.12 -0.9,-3.84 -2.23,-7.73 -2.96,-8.65 -1,-1.26 -0.24,-1.67 3.17,-1.68 18.87,-0.07 27.32,-9.45 20.46,-22.72 -1.94,-3.75 -4.63,-5.32 -4.63,-2.7 0,3.72 -7.11,9.21 -14.7,11.34 -4.46,1.25 -8.46,3.17 -8.88,4.26 -0.81,2.12 -7.17,-7.05 -12.51,-18.06 -6.56,-13.54 7.02,-42.16 18,-37.95 3.42,1.31 2.47,-2.82 -2.39,-10.46 -8.16,-12.83 -28.7,-50.96 -29.64,-55.03 -0.51,-2.2 -1.51,-4.75 -2.24,-5.67 -0.97,-1.24 -0.48,-1.67 1.92,-1.67 4.61,0 15.79,12.85 25.74,29.6 0.74,1.25 3.84,6.35 6.89,11.35 3.04,4.99 7.48,12.67 9.86,17.06 7.67,14.18 10.37,14.67 7.19,1.32 -8.92,-37.42 -13.67,-67.33 -10.68,-67.33 4.83,0 10.19,12.2 21.09,48 4.66,15.32 11.84,29.67 21.54,43.1l9.47,13.1 0.83,-18.53c1.61,-35.75 13.16,-48.09 15.99,-17.09 6.58,71.99 -13.06,113.17 -50.83,106.53zM444.67,484.14c-11,-3.61 -21.62,-6.93 -23.61,-7.37 -11.74,-2.6 -2.05,-51.46 10.6,-53.43 2.76,-0.43 7.36,-1.41 10.23,-2.18 8.95,-2.4 9.82,-2.37 12.33,0.41 4.22,4.66 9.57,6.7 15.11,5.77 5.94,-1 13.1,2.14 22.44,9.84l5.85,4.83 -0.73,10.67c-2.41,35.09 -16.1,43.34 -52.22,31.48zM120.32,385.93c-4.48,-6.83 -9.91,-9.22 -14.98,-6.6 -2.2,1.14 -5.44,2.61 -7.21,3.29l-3.22,1.22 3.97,-11.85c3.76,-11.21 3.86,-12.09 1.93,-16.25 -13.15,-28.36 -15.66,-51.74 -5.55,-51.74 1.81,0 11.34,9.83 14.53,15 4.19,6.77 8.76,5.11 14.21,-5.15 13.53,-25.51 21.63,-25.57 21.27,-0.15 -0.14,10.24 0.51,16.12 2.6,23.61 1.54,5.49 2.79,12.41 2.79,15.4 0,2.98 1.77,11.08 3.94,17.98 4.57,14.57 4.55,14.65 -2.14,10.1 -6.92,-4.7 -9.15,-4.36 -13.37,2 -4.44,6.7 -4.5,6.57 -7.8,-15.93 -2.19,-14.94 -4.07,-20.2 -7.19,-20.2 -2.85,0 -2.48,36.4 0.42,41.09 2.35,3.8 -1.65,2.06 -4.2,-1.83zM426.03,382.7c-1.31,-1.45 -3,-2.26 -3.75,-1.79 -0.78,0.48 -0.95,0.18 -0.41,-0.7 0.57,-0.92 0.09,-1.54 -1.21,-1.54 -1.19,0 -1.79,-0.6 -1.33,-1.33 0.45,-0.73 -0.01,-1.33 -1.04,-1.33 -2.93,0 -19.63,-18.04 -19.63,-21.2 0,-2.86 1.64,-3.13 19.33,-3.13 1.83,0 4.11,0.07 5.05,0.17 1.12,0.11 2.31,4.25 3.39,11.83 0.92,6.42 2,13.92 2.4,16.67 0.83,5.73 0.49,6.01 -2.81,2.36zM323.98,346.33c-9.34,-7.29 -11.16,-11.36 -7.58,-17.02l2.93,-4.64 0.74,6c1.4,11.4 6.13,17.3 15.05,18.75l5.55,0.91 -5.56,0.17c-4.4,0.13 -6.73,-0.74 -11.12,-4.17zM345.12,345.75c3.16,-5.92 0.24,-24.53 -4.45,-28.42 -4.28,-3.55 0.4,-5.66 5.95,-2.69 4.94,2.64 7.68,23.44 3.72,28.17 -3.61,4.3 -6.94,6.18 -5.22,2.94zM107.7,286.93c-3.42,-7.84 -6.94,-18.02 -7.81,-22.63 -1.94,-10.27 1.26,-8.93 -27.89,-11.69 -13.57,-1.28 -25.12,-2.73 -25.66,-3.21 -1.05,-0.92 12.85,-12.51 21.74,-18.12 6.08,-3.84 5.85,-2.01 3.19,-26.16 -1.14,-10.32 -1.87,-18.96 -1.63,-19.2 0.24,-0.24 3.62,5.21 7.51,12.13 11.47,20.39 20.28,33.81 31.11,47.41l10.19,12.79 -1.84,21.05c-1.01,11.58 -2.03,21.24 -2.26,21.47 -0.23,0.23 -3.22,-6 -6.65,-13.84zM366,198.69c-13.11,-2.37 -20.67,-4.84 -20.67,-6.76 0,-0.53 1.22,-6.89 2.71,-14.12 4.86,-23.54 7.95,-44.94 7.95,-55.04 0,-5.44 0.23,-9.64 0.51,-9.33 0.38,0.42 1.7,10.12 2.13,15.64 0.04,0.52 6.95,-0.66 14.03,-2.39 2.93,-0.71 6.16,-1.31 7.17,-1.33 1.01,-0.02 -4.24,6.28 -11.67,13.99 -15.62,16.22 -16.29,18.25 -8.84,26.95 2.57,3 4.67,5.82 4.67,6.27 0,0.45 1.41,3.05 3.13,5.79 3.82,6.06 13.17,23.02 12.61,22.86 -0.22,-0.06 -6.41,-1.2 -13.74,-2.53z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M81.94,186.19a154.12,154.12 0,0 0,33 -9.82l2.14,-0.93a10.54,10.54 0,0 0,0.11 3.18c0.27,0.77 1.1,1.78 1.78,1.85s1.53,-0.84 2,-1.52a11.55,11.55 0,0 0,0.91 -2.41c0.3,-0.71 0.7,-1.37 1.12,-2.19a43.76,43.76 0,0 0,3.64 3.11,2.27 2.27,0 0,0 2.07,0.16c0.4,-0.32 0.29,-1.36 0.27,-2.07a1.7,1.7 0,0 0,-0.46 -1.06c-3.23,-3.23 -1.86,-5.71 0.72,-8.76 8,-14.16 8.77,-22.84 5.83,-36.44 -3.51,-16.2 -6.14,-32.31 -15.74,-46.14 -3,-4.39 -4.83,-9.69 -7,-14.66a14.64,14.64 0,0 1,-0.71 -4.58c-0.83,-10.78 -3.1,-20.89 -4.48,-31.6 -0.59,-4.59 -3,-10.25 -5.23,-14.37 -2.85,-5.19 -13.68,-8.4 -17.09,-3.54 -3.66,5.21 -3.42,14.21 -4.93,20.39C78,42.44 78.64,49.65 76.06,57.56a73.93,73.93 0,0 0,-15.12 3.65,99.69 99.69,0 0,0 -5,-16.75C52.58,36 48.67,27.83 41.4,21.84c-0.53,-0.43 -1,-0.9 -1.59,-1.29 -5.14,-3.64 -9.54,-2.46 -10.72,3.69A112.24,112.24 0,0 0,27 45.71C27,54.84 28.12,64 28.7,73.1a8,8 0,0 1,-0.28 3.13C25.52,84.29 25,92.61 25.81,101a86.56,86.56 0,0 1,0.12 18.63c-1.4,12.31 2.45,23.73 7.59,34.71 4.21,9 9.53,17.26 16.72,24.17C53,181.22 55.82,184 59.4,186c1.84,1 4.4,0.6 4.46,1s-1,0.93 -3.5,3c-1.4,1.19 -1.82,1.67 -1.74,2.19s1.2,0.58 1.82,0.78a0.7,0.7 0,0 0,0.64 -0.11A12.45,12.45 0,0 0,64.29 190,7.41 7.41,0 0,0 66,187.39a22.49,22.49 0,0 0,-0.87 5.25c0,0.7 2.29,0.56 2.62,-0.44 0.44,-1.31 -0.44,-3.06 1.31,-4.37 0,0 -1,4.34 0.44,5.24a1.9,1.9 0,0 0,2.18 -0.43c1.09,-1.22 -0.61,-3.32 0,-4.81S74.78,186.52 81.94,186.19ZM79.47,61.68c0.32,-1 0.65,-1.9 0.89,-2.82C82.08,52.09 81.57,45.8 83,38.3c1.71,-8.84 0.81,-15.64 4.65,-21.69 2,-3.2 10.42,0.16 12,3.54a50.1,50.1 0,0 1,4 13.44c1.23,8.86 4.31,17.41 4.43,26.35a41.43,41.43 0,0 0,8.22 24.79,59.62 59.62,0 0,1 9.62,18.34c4.41,14.18 8.3,27.09 7.86,41.51 -1.31,10 -2.88,18.68 -15.27,26 -14.5,8.51 -30.57,12.59 -47.41,13.36a22.35,22.35 0,0 1,-17 -6.25c-8.42,-7.89 -14.4,-17.39 -19,-27.82 -4.3,-9.84 -6.91,-20 -5.89,-31a76.17,76.17 0,0 0,0.22 -14.24c-0.94,-9.72 -1,-19.26 2.55,-28.55a6.05,6.05 0,0 0,0.09 -3.14,134.53 134.53,0 0,1 -1.71,-31.74A121.63,121.63 0,0 1,32.2 25.26c0.76,-4 2.8,-4.63 5.72,-1.92a52,52 0,0 1,8.61 9.79,77.09 77.09,0 0,1 11,28.76c0.28,1.67 0.88,2.9 2.88,2.06A34.41,34.41 0,0 1,79.47 61.68Z"
android:strokeWidth="1"
android:strokeColor="?colorOnSurface" />
<path
android:fillColor="#d7d7d7"
android:pathData="m366.82,652.33c0.09,-16.68 0.37,-29.43 0.64,-28.33 0.7,2.88 5.87,51.79 5.87,55.53 0,2.46 -0.72,3.14 -3.33,3.14h-3.33zM286,451.86c-22.24,-3.2 -46.33,-12.87 -57.7,-23.14 -11.14,-10.07 -11.32,-11.41 -1.25,-9.25 9.93,2.13 18.29,2.38 18.29,0.54 0,-0.73 -0.55,-1.33 -1.22,-1.33 -4.8,0 -29.91,-27.74 -35.49,-39.21 -7.92,-16.29 -8.12,-17.92 -3.99,-32.12 5.82,-19.96 5.79,-32.65 -0.12,-62.44 -2.68,-13.5 -2.83,-13.51 9.26,0.7l10.34,12.14 -3.56,3.72c-7.47,7.79 -1.42,13.63 13.56,13.09 7.21,-0.26 9.2,0.27 16.76,4.5 31.78,17.77 42.62,16.6 47.91,-5.18 0.87,-3.58 2.66,-7.71 3.97,-9.18 1.31,-1.47 3.43,-6.42 4.7,-11 1.27,-4.58 3.06,-8.33 3.97,-8.33 2.44,0 8.57,-7.77 8.57,-10.87 0,-2.36 0.37,-2.23 3.18,1.1 1.75,2.07 9.86,9.75 18.04,17.06 20.77,18.58 26.54,43.03 19.59,83.04 -2.83,16.31 -2.85,16.21 2.53,11 5.77,-5.59 5.79,-4.92 0.22,7.14 -8.03,17.39 -26.79,42.18 -40.94,54.11l-6.42,5.42 -11.09,-0.16c-6.1,-0.09 -14.69,-0.68 -19.09,-1.32zM315.51,435.34c6.25,-4.1 8.97,-12.9 7.97,-25.85 -0.95,-12.33 -1.25,-13.22 -3.78,-11.16 -0.93,0.76 -4.78,1.79 -8.54,2.29 -5.37,0.72 -7.79,1.9 -11.26,5.48 -3.86,3.98 -5.26,4.56 -11,4.56h-6.58l0.9,5.63c2.8,17.54 19.53,27.4 32.28,19.05zM223.98,367.15c6,-2.73 23.77,-3.96 29.29,-2.04 4.01,1.4 4.18,1.32 3.33,-1.49 -1.35,-4.46 -2.19,-4.96 -10.03,-5.86 -3.98,-0.46 -7.63,-1.44 -8.11,-2.18 -0.54,-0.83 -1.72,-0.5 -3.1,0.87 -1.22,1.22 -3.22,2.22 -4.45,2.23 -4.45,0.03 -16.28,6.54 -17.44,9.6 -1.62,4.29 -0.38,5.57 2.99,3.09 1.58,-1.16 4.96,-3.07 7.52,-4.23zM340,349.11c0,-0.86 0.39,-1.16 0.87,-0.69 0.48,0.48 1.72,-0.17 2.77,-1.44 1.04,-1.27 1.44,-1.44 0.89,-0.38 -0.63,1.21 0.44,0.9 2.9,-0.85 2.15,-1.53 4.51,-2.83 5.24,-2.88 0.73,-0.05 1.79,-2.97 2.34,-6.48 2.81,-17.79 -4.73,-30.41 -17.65,-29.51 -5.95,0.41 -6.7,0.17 -6.71,-2.2 -0.05,-6.19 -2.39,-6.5 -4.12,-0.55 -1.05,3.59 -4.48,9.01 -8.78,13.89 -7.19,8.15 -9.09,14 -5.28,16.27 0.98,0.59 2.91,2.8 4.28,4.93 5.47,8.48 23.26,16.06 23.26,9.92zM409.63,443.64 L405.33,439.21 405.31,419.94c-0.01,-11.96 -1.02,-26.34 -2.67,-37.9 -1.46,-10.25 -2.65,-19.53 -2.65,-20.62 0,-1.12 3.39,1.36 7.74,5.66 4.26,4.21 8.25,7.19 8.88,6.62 0.63,-0.57 0.71,-0.29 0.18,0.63 -0.6,1.04 -0.15,1.67 1.2,1.67 1.19,0 1.79,0.6 1.33,1.33 -0.45,0.73 -0.01,1.33 0.99,1.33 3.65,0 9.85,8.43 10.23,13.91 0.2,2.88 0.59,6.42 0.87,7.87 0.28,1.45 0.9,6.95 1.38,12.24l0.87,9.61 -4.21,1.47c-4.47,1.56 -7.08,6.11 -10.07,17.57 -1.94,7.45 -4.24,8 -9.75,2.31z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M89.05,76.37a14.46,14.46 0,1 0,18.64 8.55A14.49,14.49 0,0 0,89.05 76.37ZM90.46,79.87c5.23,-1.92 11.73,1.18 13.62,6.5s-1.14,11.68 -6.23,13.49a10.66,10.66 0,0 1,-7.39 -20Z"
android:strokeWidth="1"
android:strokeColor="?colorOnSurface" />
<path
android:fillColor="#afafaf"
android:pathData="m135.96,681c-0.02,-0.92 -0.34,-6.17 -0.71,-11.67l-0.68,-10 3.95,7.33c2.17,4.03 5.76,9.28 7.97,11.67l4.03,4.33h-7.26c-4.9,0 -7.27,-0.54 -7.3,-1.67zM224.73,677.86c-0.73,-3.89 -0.26,-5.73 2.5,-9.71 4.81,-6.94 7.84,-16.63 7.03,-22.51 -8.37,-61.15 -12.36,-73.12 -24.62,-74.01 -3.46,-0.25 -3.85,-0.64 -2.74,-2.73 1.88,-3.52 2.81,-34.06 1.49,-49.05l-1.13,-12.82 4.03,-1.57 4.03,-1.57 -4.33,-1.87c-2.78,-1.2 -4.33,-2.81 -4.33,-4.5 0,-7.77 -4.25,-8.99 -9.42,-2.7l-3.42,4.16 -4.31,-4.57c-2.37,-2.52 -5.22,-6.32 -6.33,-8.46 -1.11,-2.13 -3.8,-6.29 -5.98,-9.24 -8.2,-11.09 -16.77,-22.9 -17.63,-24.3 -1.06,-1.72 1.91,-4.9 20.34,-21.72 8.3,-7.57 14.1,-11.98 14.44,-10.96 4.04,12.11 24.92,48.52 39.86,69.49l10.35,14.53 9.19,0.52 9.19,0.52 6.77,7.28c16,17.2 38.49,39.58 43.49,43.28 7.57,5.6 5.8,8.08 -4.56,6.4 -9.39,-1.52 -16.91,12.64 -10.02,18.88 4.51,4.08 13.8,4.3 16.87,0.4 1.53,-1.94 4.67,-3.67 7.7,-4.24l5.13,-0.96 -0.57,4.42c-0.31,2.43 -3.83,26.47 -7.82,53.42l-7.25,49 -43.52,0 -43.52,0 -0.9,-4.8zM283.33,540.48c-10.27,-5.57 -24.95,-13.63 -32.63,-17.9 -16.57,-9.22 -16.28,-9.3 -9.95,2.74 8.16,15.53 9.53,16.52 26.58,19.19 8.07,1.26 18.27,3.1 22.67,4.09 13.2,2.96 12.76,2.44 -6.67,-8.11zM364,681.33c0,-7.26 -9.96,-85.3 -12.76,-100 -1.05,-5.5 -1.91,-10.09 -1.91,-10.2 -0,-0.34 8.33,-3.13 9.36,-3.13 1.18,0 1.93,4.15 5.69,31.33 3.16,22.82 3.81,83.33 0.9,83.33 -0.71,0 -1.29,-0.6 -1.29,-1.34zM445.58,673.67c-6.74,-11.66 -60.24,-111.93 -60.24,-112.92 0,-0.41 2.4,-0.75 5.33,-0.75 11.75,0 14.79,-13.58 4.84,-21.64 -3.53,-2.86 -7.8,-2.97 -12.91,-0.33 -5.05,2.61 -5.07,4.95 0.27,-40.7 5.03,-43.05 2.99,-36.97 13.3,-39.74l8.49,-2.29 0.67,-7.78 0.67,-7.78 3.19,3.41c1.76,1.88 3.8,3.04 4.54,2.58 0.76,-0.47 0.93,-0.17 0.39,0.71 -0.54,0.88 -0.17,1.54 0.87,1.54 2.58,0 1.88,6.38 -1.4,12.82 -1.49,2.92 -3.15,7.67 -3.69,10.56 -0.58,3.12 -3.52,8.56 -7.21,13.38 -5.85,7.63 -24.11,48.93 -22.4,50.64 0.6,0.6 17.15,-18.13 25.99,-29.4 2.69,-3.43 4.51,-7.59 5.1,-11.67 1.41,-9.76 4.53,-8.16 5.36,2.76 0.26,3.43 2,12.16 3.86,19.4 2.38,9.25 3.33,16.24 3.19,23.52 -0.29,15.19 6.64,23.91 23.64,29.77 4.96,1.71 5.25,2.12 5.48,7.68 0.14,3.23 0.92,26.57 1.74,51.87 0.82,25.3 1.81,47.65 2.19,49.67 1.54,8.05 -5.43,4.75 -11.27,-5.33zM553.96,681c-0.34,-0.92 -8.66,-28.92 -18.49,-62.23l-17.87,-60.56 3.48,-5.44c1.91,-2.99 3.64,-5.68 3.84,-5.96 0.2,-0.29 1.39,0.31 2.64,1.33 1.96,1.59 2.13,1.59 1.16,-0.02 -0.77,-1.27 -0.63,-1.57 0.41,-0.92 0.85,0.52 1.54,1.76 1.54,2.76 0,0.99 5.77,13.3 12.83,27.35 15.67,31.2 14.88,27.74 15.97,70.35l0.9,35h-2.89c-1.59,0 -3.17,-0.75 -3.51,-1.67zM338.17,541.74c-1.9,-4.99 -6.76,-17.94 -10.81,-28.78 -15.23,-40.76 -18.34,-45.25 -29.06,-41.95 -11.17,3.44 -19.73,4.32 -21.07,2.16 -0.76,-1.24 -0.39,-2.14 1.15,-2.75 1.34,-0.53 -0.29,-0.95 -3.92,-1l-6.2,-0.09 -0.96,-8.33c-0.53,-4.58 -1.44,-9.59 -2.02,-11.12l-1.06,-2.79 10.22,2.49c5.62,1.37 17.25,2.86 25.84,3.32l15.62,0.84 8.55,-7.51 8.55,-7.51 0.83,4.14c1.52,7.62 1.06,8.85 -3.93,10.5 -2.62,0.86 -4.51,1.82 -4.21,2.12 0.3,0.3 2.99,-0.31 5.98,-1.37 5.2,-1.84 6.98,-1.21 6.98,2.45 0,0.19 -2.4,1.14 -5.33,2.1 -5.55,1.83 -5.74,2.23 -5.38,11.33 0.45,11.56 8.84,40.33 19.25,66 4.95,12.21 4.91,12.61 -1.38,13.93 -4.15,0.87 -4.22,0.79 -7.64,-8.19zM182.76,533.33c-3.25,-4.77 -6.43,-8.88 -7.07,-9.13 -0.64,-0.26 2.32,-2.12 6.57,-4.15 4.25,-2.03 7.75,-3.62 7.76,-3.53 0.02,0.08 -0.28,5.85 -0.67,12.82l-0.69,12.67zM109.29,300.42c-2.55,-4.14 -5.55,-9.07 -6.67,-10.97 -1.12,-1.9 -3.67,-5.99 -5.67,-9.09 -1.99,-3.1 -3.62,-6.22 -3.62,-6.92 0,-1.94 -1.57,-2.58 -11.54,-4.67 -10.66,-2.24 -37.8,-15.52 -37.8,-18.5 0,-0.6 0.49,-0.79 1.09,-0.42 0.6,0.37 10.35,1.6 21.67,2.74 11.32,1.13 23.07,2.39 26.12,2.8l5.54,0.74 0.95,8.27c0.93,8.1 12.74,37.74 15.89,39.88 0.94,0.64 0.95,1.38 0.03,2.31 -0.93,0.93 -2.84,-1.05 -5.99,-6.16zM426.77,295.35c-11.15,-5.92 -12.35,-7.36 -16.73,-20.02 -6.48,-18.74 -16.36,-35.57 -29.03,-49.43l-6.76,-7.4 6.54,-6.83c3.6,-3.76 7.25,-7.62 8.11,-8.59 3.42,-3.83 5.88,-0.11 7.66,11.57 1.32,8.7 3.25,13.36 7.7,18.56 8.64,10.09 13.05,22.32 8.97,24.84 -2.51,1.55 4.29,7.29 13.77,11.6 9.93,4.52 10.18,4.86 6.33,8.33 -3.5,3.16 -3.51,11.09 -0.02,17.29 1.45,2.59 2.5,4.68 2.33,4.66 -0.17,-0.03 -4.16,-2.1 -8.87,-4.6zM116.87,284.33c0.62,-7.88 1.13,-17.43 1.13,-21.22 0,-7.74 0.76,-7.64 6.68,0.9 2.81,4.05 3.32,5.71 2.17,7.09 -0.82,0.99 -1.1,2.2 -0.62,2.68 0.48,0.48 0.11,0.88 -0.82,0.88 -0.96,0 -1.37,0.87 -0.93,2 0.42,1.1 0.12,2 -0.67,2 -0.79,0 -1.09,0.9 -0.67,2 0.42,1.1 0.12,2 -0.67,2 -0.79,0 -1.09,0.9 -0.67,2 0.42,1.1 0.12,2 -0.67,2 -0.79,0 -1.09,0.9 -0.67,2 0.42,1.1 0.12,2 -0.67,2 -0.79,0 -1.09,0.9 -0.67,2 0.42,1.1 0.12,2 -0.67,2 -0.79,0 -1.09,0.9 -0.67,2 0.42,1.1 0.13,2 -0.65,2 -0.99,0 -1.08,-4.38 -0.3,-14.33zM356.95,162.79c-3.72,-6.3 -2.81,-8.11 11.81,-23.41 15.61,-16.34 16.08,-15.7 4.67,6.41 -11.77,22.8 -12.56,23.61 -16.47,17z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M51.32,106a13.27,13.27 0,1 0,-17.15 -7.9A13.29,13.29 0,0 0,51.32 106ZM37.9,96.78a9.68,9.68 0,0 1,5.52 -12,9.39 9.39,0 1,1 6.44,17.64A9.57,9.57 0,0 1,37.9 96.78Z"
android:strokeWidth="1"
android:strokeColor="?colorOnSurface" />
<path
android:fillColor="#777777"
android:pathData="m302.67,583.09c-7.71,-2.96 -9.05,-11.58 -2.77,-17.85 6.7,-6.7 14.71,-4.18 17.62,5.53 2.7,9.02 -5.57,15.88 -14.85,12.32zM339.35,569.82c-5.5,-5.5 -4.34,-14.72 2.29,-18.15 12.59,-6.51 21.23,-2.69 21.57,9.53 0.18,6.68 -18.9,13.57 -23.86,8.61zM381,557c-7.86,-8.09 -2.53,-21.08 8.6,-20.97 8.55,0.08 15.06,13.58 9.93,20.6 -3.38,4.63 -14.19,4.84 -18.53,0.37zM432,496.14c-22.95,-7.47 -28.2,-17.13 -18.68,-34.39 2.42,-4.39 2.7,-4.58 2.04,-1.38 -3,14.56 4.02,25.66 20.56,32.5 14.59,6.03 11.77,8.38 -3.92,3.27zM158.43,430.32c-26.78,-28.97 -32.81,-37.6 -34.51,-49.38 -1.53,-10.58 -1.56,-29.06 -0.05,-31.45 2.44,-3.85 3.92,0.44 6.88,19.82 3.38,22.16 3.42,22.23 8.42,14.68 4.3,-6.49 6.99,-6.72 14.92,-1.27 3.25,2.24 5.91,3.81 5.91,3.5 0,-0.31 -1.85,-6.49 -4.11,-13.73 -2.26,-7.24 -4.34,-16.62 -4.62,-20.84 -0.28,-4.22 -1.51,-10.82 -2.74,-14.67 -1.54,-4.81 -2.28,-12.03 -2.38,-23.12 -0.24,-27.24 -8.2,-27.72 -22.14,-1.32 -3.63,6.87 -5.26,8.76 -7.33,8.48 -2.87,-0.39 -4.01,-10.11 -1.68,-14.36 0.6,-1.1 3.77,-10.06 7.05,-19.92 6.71,-20.2 6.87,-18.73 -3.32,-29.97 -9.71,-10.71 -18.29,-23.17 -34.05,-49.45l-16,-26.67 -0.43,6.67c-0.24,3.67 0.6,14.06 1.86,23.09 2.1,15.09 2.11,16.6 0.14,18.57 -1.8,1.8 -2.24,1.84 -2.75,0.24 -3.48,-10.84 -6.21,-20.5 -7.49,-26.57 -0.86,-4.03 -2.05,-9.43 -2.64,-12 -2,-8.58 -4.5,-26.63 -3.79,-27.33 0.38,-0.38 3.55,0.56 7.04,2.11 5.8,2.57 21.58,7.42 28.07,8.64 1.47,0.28 8.37,1.39 15.33,2.47 23.59,3.68 40.18,8.83 59.11,18.35 8.16,4.1 8.35,4.08 14.09,-1.34 13.08,-12.38 35.87,-22.27 68.14,-29.56l12.67,-2.86 4.64,-6.9c4.69,-6.98 10.19,-14 18.7,-23.89 6.25,-7.26 20.26,-18.32 30.04,-23.72 10.39,-5.73 14.36,-9.46 21.27,-19.96 9.53,-14.48 16.95,-18.36 18.61,-9.73 5.47,28.41 2.18,84.19 -6.72,114.01 -0.77,2.57 5.65,5.85 14.09,7.2 4.45,0.71 5.03,1.31 6.47,6.71 1.19,4.44 4.18,8.68 11.97,16.95 18.44,19.57 28.01,36.56 36.91,65.53 5.12,16.66 12.3,30.62 20.3,39.48l4.62,5.11 -9.45,-3.22c-5.2,-1.77 -9.45,-2.93 -9.45,-2.58 0,0.35 -1.2,-0.01 -2.67,-0.79 -2.36,-1.26 -2.68,-1 -2.77,2.29 -0.06,2.04 -0.32,4.85 -0.58,6.25 -0.26,1.39 -0.62,5.29 -0.8,8.67 -0.18,3.37 -0.57,6.86 -0.88,7.75 -0.61,1.77 -14.94,1.81 -14.96,0.05 -0.01,-0.55 -1.18,-1 -2.6,-1 -1.42,0 -2.92,-0.54 -3.34,-1.21 -2.98,-4.82 -14.35,5.89 -13.51,12.72 0.59,4.81 0.02,6.46 -4.49,12.89 -2.84,4.06 -4.92,7.73 -4.62,8.15 0.3,0.43 0.14,0.46 -0.36,0.08 -0.5,-0.38 -2.99,1.27 -5.53,3.67 -3.43,3.24 -4.37,3.67 -3.63,1.69 10.83,-29.21 3.94,-76.21 -13.39,-91.33 -5.46,-4.77 -13.34,-12.06 -17.5,-16.21l-7.57,-7.54 -2.29,5.12c-1.27,2.85 -4,6 -6.15,7.11 -3.06,1.58 -4.19,3.45 -5.48,9.05 -0.9,3.88 -2.73,8.27 -4.07,9.76 -1.34,1.49 -3.15,5.64 -4.02,9.22 -5.43,22.34 -16.03,23.81 -46.75,6.47 -7.89,-4.45 -9.65,-4.91 -18.89,-4.95 -14.97,-0.06 -19.88,-5.16 -12.04,-12.5l3.77,-3.53 -11.98,-13.94c-8.43,-9.81 -11.72,-12.88 -11.09,-10.37 8.08,32.18 7.84,64.19 -0.61,82.74 -4.53,9.94 12.96,37.58 36.8,58.16l6.67,5.75 -10.67,-0.92c-5.87,-0.5 -11.97,-1.03 -13.56,-1.17 -1.59,-0.14 -2.67,-0.47 -2.41,-0.73 0.26,-0.26 -0.1,-1.16 -0.79,-2 -0.7,-0.84 -2.54,-3.21 -4.09,-5.28 -2.49,-3.32 -3.4,-3.66 -7.85,-2.91 -3.05,0.51 -5.38,0.29 -5.91,-0.58 -1.73,-2.81 -3.49,-1.43 -1.92,1.51 1.34,2.51 1.15,3.21 -1.29,4.83 -1.57,1.04 -2.86,2.59 -2.86,3.45 0,1.42 -4.66,6.18 -19.38,19.79l-5.38,4.97 -10.15,-10.99zM298,433.58c-2.93,-1.37 -6.15,-3.48 -7.15,-4.69 -1,-1.21 -2.34,-1.87 -2.98,-1.48 -0.64,0.4 -0.77,-0.31 -0.29,-1.57 0.59,-1.54 0.37,-1.98 -0.67,-1.33 -1.01,0.62 -1.27,0.24 -0.75,-1.11 0.44,-1.14 0.28,-2.07 -0.35,-2.07 -0.63,-0 -1.27,-1.8 -1.42,-4 -0.32,-4.54 -0.2,-4.58 5.86,-2.39 4.32,1.56 4.62,1.43 10.81,-4.67 7.87,-7.75 18.42,-6.18 19,2.83 0.03,0.49 0.55,3.46 1.16,6.59 0.62,3.22 0.59,6.01 -0.08,6.43 -0.65,0.4 -0.84,1.29 -0.41,1.97 0.42,0.68 0.13,1.24 -0.64,1.24 -0.78,0 -1.41,1 -1.41,2.22 0,1.22 -0.39,1.83 -0.88,1.34 -0.48,-0.48 -1.56,-0.31 -2.4,0.39 -3.24,2.69 -11.94,2.84 -17.39,0.29z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M96.25,93.49c1.35,-0.59 3,-1.64 2.15,-4.11s-3.71,-4 -5.71,-3.35c-2.22,0.78 -3.79,4.16 -2.91,6.25C90.55,94.09 93,94.69 96.25,93.49Z"
android:strokeWidth="1"
android:strokeColor="?colorOnSurface" />
<path
android:fillColor="#5a5a5a"
android:pathData="m457.25,681.33c-0.07,-1.1 -1.13,-26.32 -2.35,-56.05l-2.23,-54.05 6.62,1.72c14.2,3.69 28.88,1.58 45.38,-6.5 4.95,-2.42 6.94,-3 5.24,-1.52 -23.22,20.19 -41.19,60.08 -49.16,109.1 -0.98,6.01 -3.27,10.82 -3.49,7.31zM147.18,620.22c-2.28,-1.44 -5.46,-4.46 -7.05,-6.7l-2.91,-4.08 6.39,2.94c18.12,8.33 47.05,-0.67 54.65,-17.01l1.87,-4.02 -0.63,6c-0.65,6.15 -10.27,17.33 -14.92,17.33 -1.01,0 -5.16,1.52 -9.23,3.39 -10.29,4.71 -22.64,5.66 -28.16,2.17zM126.93,583.73c-4.38,-4.38 -1.44,-7.83 9.31,-10.92 6.99,-2 11.71,-5.69 13.5,-10.55 1.01,-2.74 3.69,0.9 5.02,6.84 2.64,11.74 -19.22,23.23 -27.83,14.63zM277.33,545.94c-8.43,-1.67 -17.13,-3.1 -19.33,-3.19 -6.07,-0.23 -22,-19.73 -22,-26.93 0,-0.85 59.11,30.83 60.93,32.66 1.3,1.3 -2.93,0.76 -19.6,-2.54zM380.52,534.09c7.68,-21.64 16.57,-41.4 21.8,-48.47l6.15,-8.31 1.79,5.02c4.15,11.65 1.16,18.23 -18.67,41.02 -11.84,13.61 -12.19,13.95 -11.06,10.75zM124.52,511.34c-3.5,-4.76 -6.51,-9.65 -6.68,-10.87 -0.18,-1.21 -1.11,-2.7 -2.08,-3.32 -0.97,-0.61 -1.3,-1.12 -0.75,-1.13 0.56,-0.01 -2.59,-7.21 -7,-16 -13.42,-26.76 -10.91,-23.59 8.41,10.65 5.59,9.9 11.71,20.55 13.61,23.67 5.32,8.72 1.62,6.71 -5.51,-3zM146.09,512.62c-2.2,-3.36 -4.85,-13.56 -7.43,-28.62 -2.56,-14.95 -3,-18.11 -3.2,-22.92 -0.11,-2.71 -0.55,-5.27 -0.97,-5.7 -0.81,-0.81 0.08,-16.97 0.99,-17.93 0.29,-0.31 0.53,2.81 0.53,6.93 0,7.96 3.09,25.41 9.46,53.5 4.15,18.29 4.23,20.24 0.63,14.75zM456,499.33c-4.03,-0.98 -7.63,-1.73 -8,-1.67 -2.43,0.36 -22,-9.22 -26.33,-12.89 -10.92,-9.25 -4.69,-9.26 22.68,-0.03 33.86,11.41 49.59,3.77 52.62,-25.54 1.12,-10.87 3.04,-10.08 3.04,1.25 0,30.84 -16.6,45.51 -44,38.89zM321.6,421.09c-1.18,-16.01 -10.35,-20.86 -20.5,-10.86l-6.33,6.23 -5.39,-1.78c-7.01,-2.31 -6.82,-3.35 0.63,-3.35 5.25,0 6.6,-0.64 10.6,-5 3.87,-4.23 5.43,-5 10.07,-5 3.02,0 6.63,-0.61 8.04,-1.36 3.39,-1.82 4.22,1.57 3.61,14.71 -0.26,5.49 -0.59,8.37 -0.73,6.4z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M48.33,97.4A4.61,4.61 0,0 0,51.12 92c-0.64,-1.89 -3.84,-2.79 -6.34,-1.78a4,4 0,0 0,-2 5.5C43.66,97.63 45.39,98.53 48.33,97.4Z"
android:strokeWidth="1"
android:strokeColor="?colorOnSurface" />
<path
android:fillColor="#333333"
android:pathData="m168.3,669.31c-10.12,-1.48 -21.12,-7.25 -25.01,-13.13 -9.96,-15.05 -17.41,-44.19 -14.8,-57.85 1.25,-6.52 2.59,-6.32 4.49,0.67 5.29,19.49 16.83,26.48 35.74,21.63 19.61,-5.04 29.41,-13.01 31.27,-25.43 4.9,-32.86 18.91,-30.93 25.76,3.56 8.76,44.09 6.83,50.86 -18.08,63.41 -14.63,7.37 -24.96,9.25 -39.37,7.15zM460.67,560.88c-17.4,-5.76 -36.62,-22.28 -36.69,-31.53 -0.01,-1.85 -1.5,-9.06 -3.31,-16.02 -1.8,-6.97 -3.29,-14.88 -3.31,-17.59l-0.03,-4.93 7.12,3.26c8.51,3.9 18.05,5.87 21.76,4.5 1.48,-0.55 6.23,-0.16 10.57,0.86 25.84,6.08 43.22,-8.5 43.22,-36.24 0,-8.3 0.67,-8.65 6.22,-3.22 3.09,3.02 4.25,5.58 4.86,10.66 1.27,10.68 4.86,24.12 8.51,31.81 13.66,28.77 -26.94,69.04 -58.93,58.44zM172.82,518.57c-1.33,-2.25 -2.21,-4.27 -1.95,-4.49 5.85,-5.07 22.7,-15.29 21.93,-13.31 -0.55,1.41 -1.32,5.32 -1.73,8.7 -0.7,5.87 -1.04,6.29 -7.93,9.67 -8.7,4.27 -7.43,4.33 -10.32,-0.56zM264.48,506c-0.58,-1.5 -3,-2 -9.76,-2h-8.99l-10.75,-15c-13.46,-18.77 -25.36,-38.71 -34.44,-57.67l-7.02,-14.67 2.74,-2.93c1.51,-1.61 2.44,-3.71 2.07,-4.67 -0.49,-1.28 1.09,-1.73 6.08,-1.73h6.74l4.39,7.52c7.88,13.48 21.59,23.87 39.68,30.08 9.32,3.2 10.1,3.75 10.1,7.16 0,2.03 0.41,4.77 0.92,6.08 0.81,2.1 0.3,2.02 -4.21,-0.63 -4.41,-2.6 -5.65,-2.8 -8.91,-1.45 -2.08,0.86 -5.29,1.28 -7.13,0.93 -1.84,-0.35 -3.34,-0.48 -3.34,-0.29 0,0.92 9.91,17.53 19.05,31.94 9.85,15.52 10.05,16.01 6.86,16.42 -1.81,0.23 -2.94,0.98 -2.52,1.67 0.42,0.68 0.41,1.24 -0.02,1.24 -0.43,0 -1.13,-0.9 -1.55,-2zM386.92,455c-1.54,-4.03 -4.21,-28.77 -4.35,-40.33 -0.16,-13 -1.81,-13.42 -4.54,-1.17 -1.37,6.14 -2.99,12.57 -3.6,14.28l-1.11,3.11 -4.09,-3.44c-5.25,-4.41 -5.45,-4.38 -8.59,1.47 -8.12,15.13 -11.41,17.32 -15.53,10.34l-2.8,-4.75 -3.82,3.66c-6.03,5.78 -4.99,0.07 1.22,-6.69 11.41,-12.41 26.36,-37.15 29.08,-48.12 0.54,-2.18 3.06,-6.86 5.6,-10.4 4.11,-5.72 4.53,-7.07 3.81,-12.3 -0.92,-6.75 0.75,-9.26 7.11,-10.66 2.21,-0.48 4.01,-1.39 4.01,-2.01 0,-0.62 1.72,-0.27 3.82,0.78 6.09,3.04 10.78,36.92 10.82,78.17l0.03,27.62 -6.91,2.05c-8.86,2.63 -8.53,2.68 -10.17,-1.62zM326.88,344.01c-6.07,-6.07 -8.16,-20.99 -3.71,-26.55 8.42,-10.53 19.69,-2.24 22.79,16.77 1.08,6.6 -1.38,13.13 -3.51,9.33 -3.35,-5.98 -11.72,-4.33 -9.74,1.91 1.18,3.7 -1.28,3.09 -5.83,-1.47z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M121.32,84.4 L128.48,62c0.93,-2.88 1.67,-9 5.08,-9.14 2.75,-0.11 7.08,2.25 9.73,3.16 3.51,1.21 7,2.47 10.53,3.64 3.31,1.1 8,1.52 11,3.33 1.87,1.15 3.29,4.78 4.1,6.74a48.92,48.92 0,0 1,2.78 8.59c1.33,6.43 -1.94,13.48 -3.61,19.76 -1.79,6.74 -3.53,13.49 -5.44,20.2 -0.9,3.17 -1.83,6.33 -2.83,9.47 -0.45,1.43 -0.76,3.75 -1.65,5 0,1.24 -0.39,1.58 -1.24,1l-2.39,-0.77c-6.61,-2.11 -13.24,-4.18 -19.85,-6.31 -2.15,-0.7 -3.07,2.68 -0.93,3.37 4.4,1.42 8.8,2.81 13.2,4.2 3.06,1 7.86,3.63 11.07,3.38 2.36,-0.19 2.81,-2 3.51,-4 1.38,-3.9 2.58,-7.88 3.74,-11.86 2.71,-9.27 5,-18.65 7.57,-28 1,-3.63 2.83,-7.74 3.09,-11.48 0.2,-2.79 -1,-6.24 -1.84,-8.85 -1.43,-4.26 -3.33,-11.21 -7.51,-13.54 -3.42,-1.91 -8.09,-2.45 -11.81,-3.69 -4.7,-1.56 -9.35,-3.27 -14.06,-4.83 -2.73,-0.91 -6.59,-3 -9.58,-2.49 -2.5,0.46 -2.91,2.52 -3.61,4.66 -3.29,9.9 -6.37,19.88 -9.56,29.81 -0.68,2.15 2.69,3.07 3.38,0.93Z" />
<path
android:fillColor="#0a0a0a"
android:pathData="m353.53,330.29c-0.71,-22.64 -21.79,-25.82 -35.68,-5.39 -4.41,6.49 -5.74,7.7 -6.26,5.71 -0.45,-1.71 1.8,-5.46 7.02,-11.66 4.43,-5.27 8.26,-11.36 9.06,-14.38l1.38,-5.23 0.14,4.78 0.14,4.78 7.3,-0.61c12.08,-1 19.85,9.75 17.68,24.46 -0.4,2.71 -0.64,1.96 -0.78,-2.45z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M132.76,56.37a34.42,34.42 0,0 0,-3.06 9.42c-0.36,2.2 3,3.15 3.38,0.93a31.17,31.17 0,0 1,2.7 -8.58c1,-2 -2,-3.79 -3,-1.77Z" />
<path
android:fillColor="#0a0a0a"
android:pathData="m213.33,370.85c0,-2.97 11.8,-10.84 16.28,-10.86 0.95,-0 3.37,-0.87 5.39,-1.92 2.02,-1.05 3.67,-1.35 3.67,-0.66 0,0.69 3.32,1.25 7.38,1.25 6.51,0 11.03,2.42 8.46,4.52 -1.42,1.16 -18.37,-0.98 -17.61,-2.22 0.55,-0.9 -0.28,-1.08 -2.35,-0.53 -1.76,0.47 -4.11,0.94 -5.21,1.03 -1.1,0.1 -0.2,0.63 2,1.19l4,1.02 -4.33,0.36c-2.38,0.2 -5.08,0.42 -6,0.49 -0.92,0.07 -1.97,0.46 -2.33,0.86 -2.17,2.38 -9.33,6.57 -9.33,5.46z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M139.22,57.27a21,21 0,0 0,-4 9.44c-0.35,2.21 3,3.16 3.37,0.93A18.93,18.93 0,0 1,142.24 59c1.35,-1.8 -1.69,-3.55 -3,-1.76Z" />
<path
android:fillColor="?colorPrimary"
android:pathData="m457.6,571.97c-23.02,-6.03 -32.58,-14.58 -33.34,-29.82l-0.47,-9.48 3.43,4.91c26.73,38.3 87.45,27.04 95.19,-17.65l1.03,-5.93 1.6,6c9.25,34.78 -26.84,62.6 -67.43,51.97z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M144,59.9l-2.4,8.66a1.75,1.75 0,0 0,3.38 0.93l2.4,-8.65A1.76,1.76 0,0 0,144 59.9Z" />
<path
android:fillColor="?colorPrimary"
android:pathData="m374.67,680.77c0,-9.44 -9.93,-90.09 -12.78,-103.85 -2.29,-11.06 -2.3,-10.25 0.11,-10.25 2.63,0 2.67,-7.81 0.07,-14.04 -1.56,-3.73 -2.66,-4.62 -5.71,-4.62 -4.28,0 -3.86,0.86 -15.13,-30.96 -7.45,-21.03 -11.35,-35.59 -12.62,-47.13l-0.89,-8.1 6.07,-2.32c5.77,-2.2 7.03,-4.89 3.51,-7.49 -3.2,-2.36 -2.52,-10.27 1.19,-13.82l3.83,-3.67 2.8,4.75c4.12,6.98 7.41,4.79 15.53,-10.34 3.14,-5.85 3.34,-5.88 8.59,-1.47l4.09,3.44 1.11,-3.11c0.61,-1.71 2.23,-8.14 3.6,-14.28 2.77,-12.45 4.38,-11.81 4.56,1.84 0.06,4.4 1.01,16.1 2.12,26l2.02,18 -4.78,38.59c-2.63,21.23 -4.5,39.68 -4.15,41.01 0.35,1.33 -0.02,4.14 -0.81,6.24 -1.7,4.5 0.61,11.6 4.16,12.81 1.2,0.41 3.1,2.68 4.22,5.04 2.64,5.61 57.58,108.35 61.35,114.74l2.89,4.9h-37.47c-31.53,0 -37.47,-0.3 -37.47,-1.9z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M148.93,62.23a25.59,25.59 0,0 1,-0.94 7.83,1.76 1.76,0 0,0 3.38,0.94 28.9,28.9 0,0 0,1.06 -8.77c-0.07,-2.25 -3.57,-2.26 -3.5,0Z" />
<path
android:fillColor="?colorPrimary"
android:pathData="m313.95,678.33c0.99,-5.78 15.38,-102.42 15.38,-103.28 0,-0.69 -4.7,0.28 -8.54,1.75 -1.63,0.63 -2.13,-0.07 -2.17,-2.99 -0.03,-2.1 -0.88,-5.31 -1.9,-7.14 -1.68,-3.02 -1.59,-3.47 0.91,-4.82 2.59,-1.4 2.33,-1.81 -4.44,-7.11 -16.17,-12.66 -48.89,-48.63 -44.93,-49.39l3.6,-0.68 -10.15,-16c-9.14,-14.41 -19.05,-31.02 -19.05,-31.94 0,-0.19 1.5,-0.07 3.34,0.29 1.84,0.35 5.05,-0.07 7.13,-0.93 5.04,-2.09 13.89,2.63 13.31,7.09 -0.54,4.2 1.24,6.16 5.63,6.16 2.16,0 3.85,0.45 3.76,1 -0.64,4.08 0.35,4.55 8.49,4.03 4.58,-0.29 11.16,-1.36 14.61,-2.37 10.14,-2.98 12.86,1.48 31.5,51.55l10.5,28.21 -3.3,1.51c-6.95,3.17 -1.03,19.28 6.79,18.5l4.24,-0.42 2.9,17.33c1.6,9.53 4.77,33.23 7.04,52.67 2.28,19.43 4.43,36.68 4.79,38.33l0.65,3H338.63,313.21Z"
android:strokeWidth="1.33333" />
android:fillColor="?colorOnSurface"
android:pathData="M155.87,63l-2.58,9.22a1.75,1.75 0,0 0,3.38 0.93L159.24,64a1.75,1.75 0,0 0,-3.37 -0.93Z" />
<path
android:fillColor="?colorPrimary"
android:pathData="m146.58,677.69c-8.21,-9.35 -13.75,-23.51 -14.23,-36.36l-0.42,-11.33 4.42,11.39c7.61,19.63 14.84,25.5 34.63,28.15 22.05,2.95 53.63,-11.8 58.98,-27.54l1.81,-5.33 0.89,4c0.49,2.2 1.16,4.84 1.49,5.87 2.31,7.29 -9.43,26.59 -19.91,32.73 -5.74,3.37 -6.14,3.4 -34.55,3.4h-28.74z"
android:strokeWidth="1.33333" />
<path
android:fillColor="#ffffff"
android:pathData="m336.58,349.6c-6.62,-1.18 -9.81,-2.97 -12.49,-7 -2.18,-3.28 -3.24,-6.74 -4.11,-13.5 -0.3,-2.35 -0.59,-4.32 -0.63,-4.36 -0.11,-0.12 0,-0.28 -2.07,3.03 -2.21,3.55 -2.61,4.61 -2.59,6.82l0.02,1.64 -1.77,-1.62c-1.92,-1.75 -2.22,-2.3 -2.22,-4 0,-2.65 2.25,-6.8 6.2,-11.44 5.52,-6.48 8.34,-10.86 9.71,-15.09 0.54,-1.66 1.76,-4.07 1.96,-3.87 0.25,0.25 -1.3,5.17 -2.24,7.1 -1.43,2.95 -4.31,7.16 -8.18,11.97 -5.61,6.99 -7.22,9.82 -6.58,11.64 0.54,1.54 1.91,0.39 5.42,-4.59 2.09,-2.96 4.2,-5.71 4.29,-5.61 0.03,0.03 -0.08,0.73 -0.24,1.55 -0.16,0.82 -0.29,2.71 -0.3,4.2 -0.02,5.91 1.69,11.81 4.61,15.92 1.64,2.31 5.17,5.25 6.66,5.55 0.6,0.12 0.72,0.06 0.94,-0.41 0.2,-0.44 0.18,-0.83 -0.1,-1.94 -0.44,-1.77 -0.43,-2.38 0.06,-3.46 0.5,-1.1 1.4,-1.75 2.85,-2.05 2.24,-0.47 4.7,0.79 6.37,3.25 0.98,1.45 1.67,1.78 2.38,1.14 0.6,-0.54 0.99,-1.34 1.36,-2.8l0.32,-1.23 -0.12,1.33c-0.22,2.47 -0.61,3.36 -2.15,4.83 -1.72,1.64 -2.49,2.06 -3.09,1.68 -0.59,-0.37 -0.79,-0.18 -0.95,0.93 -0.08,0.53 -0.2,0.95 -0.28,0.94 -0.07,-0.01 -1.43,-0.25 -3.02,-0.54z"
android:strokeWidth="0.21377" />
<path
android:fillColor="#ffffff"
android:pathData="m302.04,437.85c-3.5,-0.82 -6.47,-2.29 -9.34,-4.6 -2.03,-1.64 -5.08,-5.3 -4.72,-5.66 0.37,-0.37 1.49,0.16 2.39,1.12 1.09,1.16 4.2,3.28 6.56,4.47 3.37,1.69 5.64,2.19 10,2.2 4.26,0.01 6.13,-0.39 8.32,-1.78 1.24,-0.79 2.26,-1.01 2.54,-0.55 0.15,0.25 -1.93,1.93 -3.65,2.96 -3.05,1.82 -8.61,2.67 -12.1,1.85z"
android:strokeWidth="0.271863" />
<path
android:fillColor="#ffffff"
android:pathData="m349.04,344.35c0.14,-0.18 0.57,-0.67 0.95,-1.1 0.79,-0.9 1.1,-1.47 1.49,-2.71 0.56,-1.79 0.69,-3 0.68,-6.73 -0,-2.62 -0.05,-3.8 -0.2,-5.02 -0.8,-6.45 -2.55,-11.67 -4.56,-13.59 -1.09,-1.04 -3.77,-1.97 -5.71,-1.97 -1.2,0 -1.94,0.22 -2.37,0.72 -0.71,0.81 -0.38,1.79 1.06,3.18 1.55,1.49 2.55,3.2 3.55,6.08 1.16,3.31 2.14,8.33 2.27,11.58 0.05,1.16 0.04,1.18 -0.06,0.39 -0.06,-0.45 -0.23,-1.5 -0.39,-2.32 -1.69,-8.93 -5.34,-15.78 -9.92,-18.64 -0.98,-0.61 -2.83,-1.33 -3.43,-1.34 -0.79,-0.01 -0.6,-0.19 0.59,-0.59 2.12,-0.7 3.14,-0.87 5.31,-0.89 1.25,-0.01 2.2,0.04 2.7,0.14 5.47,1.08 9.46,5.11 11.35,11.49 0.64,2.14 0.91,4.08 1.12,7.85 0.15,2.82 0.26,3.6 0.48,3.47 0.05,-0.03 0.18,-0.5 0.27,-1.03 1.14,-6.38 0.16,-13.01 -2.56,-17.48 -0.87,-1.43 -1.55,-2.3 -2.61,-3.34 -2.7,-2.66 -6.1,-4.11 -10.05,-4.31 -0.98,-0.05 -2.33,0.02 -5.43,0.28 -2.27,0.19 -4.14,0.33 -4.17,0.3 -0.08,-0.08 -0.29,-8.79 -0.22,-8.87 0.04,-0.04 0.22,0.07 0.4,0.24 0.57,0.54 0.84,1.7 0.98,4.12 0.1,1.76 0.23,2.11 0.95,2.49 0.4,0.21 0.62,0.23 2.54,0.23 1.15,-0 3.01,-0.02 4.14,-0.04 1.58,-0.03 2.29,0 3.14,0.15 8.74,1.52 14.16,9.98 14.18,22.14 0,4.04 -0.66,8.68 -1.66,11.5 -0.55,1.56 -0.79,1.9 -1.49,2.13 -0.67,0.22 -1.53,0.65 -2.65,1.31 -0.46,0.27 -0.87,0.5 -0.9,0.5 -0.03,0 0.06,-0.14 0.2,-0.32z"
android:strokeWidth="0.11033" />
<path
android:fillColor="#ffffff"
android:pathData="m213.11,372.21c-0.39,-0.39 -0.28,-1.82 0.25,-3.35 0.3,-0.86 0.6,-1.29 1.59,-2.24 1.67,-1.6 4.54,-3.47 7.95,-5.15 3.3,-1.63 5.43,-2.38 7.56,-2.66 1.89,-0.25 3.28,-0.88 4.8,-2.17 0.64,-0.54 1.33,-1.07 1.54,-1.18 0.55,-0.29 1.22,-0.25 1.49,0.08 0.68,0.82 3.48,1.62 7.64,2.2 6.23,0.86 7.5,1.18 8.64,2.18 0.76,0.67 1.56,2.29 2.06,4.2 0.58,2.19 0.68,2.17 -3.67,0.86 -1.95,-0.58 -4.44,-0.82 -8.6,-0.82 -6.52,0 -13.55,0.8 -17.92,2.05 -2.39,0.68 -7.38,3.2 -9.86,4.98 -1.78,1.28 -2.9,1.61 -3.48,1.03zM215.76,370.3c2.05,-1.05 4.72,-2.81 6.26,-4.13 0.68,-0.59 1.52,-1.16 1.86,-1.28 0.46,-0.16 9.09,-1 11.39,-1.1 0.49,-0.02 -0.39,-0.32 -2.94,-0.98 -2.62,-0.68 -3.59,-1.05 -3.11,-1.2 0.15,-0.04 0.87,-0.17 1.6,-0.29 0.73,-0.11 2.19,-0.41 3.24,-0.66 1.05,-0.25 2.13,-0.43 2.4,-0.4 0.43,0.05 0.49,0.13 0.44,0.55 -0.05,0.42 0.04,0.53 0.61,0.77 2.33,0.97 12,2.21 15.43,1.98 1.21,-0.08 1.49,-0.16 1.87,-0.55 0.61,-0.61 0.62,-1.53 0.02,-2.16 -0.59,-0.63 -2.31,-1.41 -3.91,-1.77 -0.78,-0.18 -2.77,-0.37 -4.8,-0.46 -5.06,-0.23 -6.93,-0.56 -7.31,-1.26 -0.32,-0.6 -1.69,-0.37 -3.99,0.69 -2.73,1.26 -3.83,1.64 -5.4,1.85 -1.76,0.24 -3.04,0.69 -5.45,1.88 -3.47,1.72 -8.39,5.27 -9.72,7.02 -0.58,0.76 -1.05,1.66 -1.05,2.02 0,0.5 0.94,0.31 2.56,-0.52z"
android:strokeWidth="0.177896" />
android:fillColor="?colorOnSurface"
android:pathData="M161.44,64.86a64.54,64.54 0,0 1,-2.72 9.35c-0.75,2.13 2.63,3 3.38,0.93a65.83,65.83 0,0 0,2.72 -9.35c0.42,-2.2 -2.95,-3.14 -3.38,-0.93Z" />
</vector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,81 +0,0 @@
<?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:animateLayoutChanges="true"
android:orientation="vertical">
<org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
android:id="@+id/checkableGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="horizontal"
android:weightSum="3">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/list"
app:icon="@drawable/ic_list" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list_detailed"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/detailed_list"
app:icon="@drawable/ic_list_detailed" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_grid"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@string/grid"
app:icon="@drawable/ic_grid" />
</org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup>
<TextView
android:id="@+id/textView_grid_title"
style="?materialAlertDialogTitleTextStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="?attr/dialogPreferredPadding"
android:paddingRight="?attr/dialogPreferredPadding"
android:singleLine="true"
android:text="@string/grid_size"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:stepSize="5"
android:valueFrom="50"
android:valueTo="150"
android:visibility="gone"
app:labelBehavior="floating"
app:tickVisible="false"
tools:value="100"
tools:visibility="visible" />
</LinearLayout>

View File

@@ -94,6 +94,7 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:clickable="true"
app:itemActiveIndicatorStyle="@style/Widget.Kotatsu.BottomNavigationView.ActiveIndicator"
app:layout_insetEdge="bottom"
app:menu="@menu/nav_bottom"
tools:ignore="KeyboardInaccessibleWidget" />

View File

@@ -1,75 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
<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:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<LinearLayout
<org.koitharu.kotatsu.base.ui.widgets.BottomSheetHeaderBar
android:id="@+id/headerBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
app:title="@string/options" />
<org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
android:id="@+id/checkableGroup"
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
android:layout_marginHorizontal="?attr/dialogPreferredPadding"
android:orientation="vertical">
android:orientation="vertical"
android:paddingBottom="@dimen/margin_normal">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list"
style="@style/Widget.Kotatsu.ToggleButton"
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/list"
app:icon="@drawable/ic_list" />
android:layout_marginHorizontal="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_normal"
android:text="@string/list_mode"
android:textAppearance="?textAppearanceTitleSmall" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list_detailed"
style="@style/Widget.Kotatsu.ToggleButton"
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/checkableGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/detailed_list"
app:icon="@drawable/ic_list_detailed" />
android:layout_marginHorizontal="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:baselineAligned="false"
android:orientation="horizontal"
app:selectionRequired="true"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_grid"
style="@style/Widget.Kotatsu.ToggleButton"
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list"
style="@style/Widget.Kotatsu.ToggleButton.Vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/compact"
app:icon="@drawable/ic_list" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_list_detailed"
style="@style/Widget.Kotatsu.ToggleButton.Vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/details"
app:icon="@drawable/ic_list_detailed" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_grid"
style="@style/Widget.Kotatsu.ToggleButton.Vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/grid"
app:icon="@drawable/ic_grid" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<TextView
android:id="@+id/textView_grid_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/grid"
app:icon="@drawable/ic_grid" />
android:layout_marginHorizontal="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_normal"
android:singleLine="true"
android:text="@string/grid_size"
android:textAppearance="?textAppearanceTitleSmall"
android:visibility="gone"
tools:visibility="visible" />
</org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup>
<com.google.android.material.slider.Slider
android:id="@+id/slider_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:stepSize="5"
android:valueFrom="50"
android:valueTo="150"
android:visibility="gone"
app:labelBehavior="floating"
app:tickVisible="false"
tools:value="100"
tools:visibility="visible" />
<TextView
android:id="@+id/textView_grid_title"
style="?materialAlertDialogTitleTextStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="?attr/dialogPreferredPadding"
android:singleLine="true"
android:text="@string/grid_size"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_grid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:stepSize="5"
android:valueFrom="50"
android:valueTo="150"
android:visibility="gone"
app:labelBehavior="floating"
app:tickVisible="false"
tools:value="100"
tools:visibility="visible" />
</LinearLayout>
</ScrollView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View File

@@ -1,9 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonRecyclerView
<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonScalingFrame
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recyclerView"
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonLayoutManager" />
android:layout_height="match_parent">
<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layoutManager="org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonLayoutManager" />
</org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonScalingFrame>

View File

@@ -6,7 +6,8 @@
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_marginVertical="8dp"
android:layout_marginHorizontal="16dp"
app:contentPadding="@dimen/margin_normal">
<androidx.constraintlayout.widget.ConstraintLayout
@@ -61,4 +62,4 @@
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.card.MaterialCardView>

View File

@@ -1,15 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:background="?selectableItemBackground"
android:drawableStart="?android:listChoiceIndicatorMultiple"
android:drawablePadding="10dp"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:text="@tools:sample/lorem[2]" />
android:minHeight="?android:listPreferredItemHeightSmall"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="8dp"
android:layout_marginStart="?attr/dialogPreferredPadding"
android:layout_marginEnd="?attr/dialogPreferredPadding"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:text="@tools:sample/lorem[15]" />
<TextView
android:id="@+id/textView_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="English" />
</LinearLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="?attr/dialogPreferredPadding" />
</LinearLayout>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="new_chapters">
<item quantity="zero">%1$d فصل جديد</item>
<item quantity="one"></item>
<item quantity="two"></item>
<item quantity="few"></item>
<item quantity="many">%1$d فصول جديدة</item>
<item quantity="other"></item>
</plurals>
<plurals name="chapters">
<item quantity="zero"></item>
<item quantity="one">%1$d فصل</item>
<item quantity="two">%1$d فصلين</item>
<item quantity="few">%1$d بعض فصول</item>
<item quantity="many">%1$d عدة فصول</item>
<item quantity="other"></item>
</plurals>
</resources>

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="detailed_list">تفاصيل القائمة</string>
<string name="error_occurred">حدث خطأ</string>
<string name="details">تفاصيل</string>
<string name="grid">شبكة</string>
<string name="list_mode">وضع القائمة</string>
<string name="settings">إعدادات</string>
<string name="remote_sources">المصادر البعيدة</string>
<string name="close_menu">غلق قائمة</string>
<string name="open_menu">فتح قائمة</string>
<string name="chapters">فصول</string>
<string name="favourites">المفضلة</string>
<string name="network_error">تعذر الاتصال بالإنترنت</string>
<string name="loading_">جار التحميل…</string>
<string name="chapter_d_of_d">فصل %1$d في %2$d</string>
<string name="close">غلق</string>
<string name="try_again">حاول مجدداً</string>
<string name="computing_">جاري الحوسبة …</string>
<string name="local_storage">التخزين المحلي</string>
<string name="history">سجل</string>
<string name="list">قائمة</string>
<string name="clear_history">محو سجل</string>
<string name="add_to_favourites">ضع هذا في المفضلة</string>
<string name="add">أضف</string>
<string name="enter_category_name">أدخل اسم القائمة</string>
<string name="save">حفظ</string>
<string name="history_is_empty">لا سجل بعد</string>
<string name="downloads">التحميلات</string>
<string name="by_name">اسم</string>
<string name="newest">الأحدث</string>
<string name="by_rating">تقييم</string>
<string name="pages">صفحات</string>
<string name="read">اقرأ</string>
<string name="share">شارك</string>
<string name="nothing_found">لم يتم عثور على اي شيء</string>
<string name="you_have_not_favourites_yet">لا مفضلة بعد</string>
<string name="search">بحث</string>
<string name="search_manga">البحث في المانجا</string>
<string name="manga_downloading_">جاري التنزيل…</string>
<string name="create_shortcut">انشاء اختصار…</string>
<string name="theme">مظهر</string>
<string name="automatic">حسب النظام</string>
<string name="share_s">شارك %s</string>
<string name="processing_">في طور معالجة…</string>
<string name="updated">محدث</string>
<string name="filter">فلتر</string>
<string name="sort_order">ترتيب الفرز</string>
<string name="light">ضوء</string>
<string name="dark">داكن</string>
<string name="clear">أزل</string>
<string name="remove">ازالة</string>
<string name="popular">شائع</string>
<string name="add_new_category">قائمة جديدة</string>
<string name="download_complete">تم التنزيل</string>
<string name="text_clear_history_prompt">هل تريد محو سجل القراءة بالكامل بشكل دائم؟</string>
</resources>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="items">
<item quantity="one">%1$dটি আইটেম</item>
<item quantity="other">%1$dটি আইটেম</item>
</plurals>
<plurals name="pages">
<item quantity="one">সর্বমোট %1$dটি পেজ</item>
<item quantity="other">সর্বমোট %1$dটি পেজ</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$dটি নতুন চ্যাপ্টার</item>
<item quantity="other">%1$dটি নতুন চ্যাপ্টার</item>
</plurals>
</resources>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="close_menu">মেনু বন্ধ করুন</string>
<string name="favourites">প্রিয়গুলি</string>
<string name="open_menu">মেনু খুলুন</string>
<string name="history">ইতিহাস</string>
<string name="local_storage">লোকাল স্টোরেজ</string>
</resources>

View File

@@ -266,7 +266,7 @@
<string name="suggestions_excluded_genres">Genres ausschließen</string>
<string name="suggestions_excluded_genres_summary">Geben Sie Genres an, die Sie nicht in den Vorschlägen sehen möchten</string>
<string name="text_delete_local_manga_batch">Ausgewählte Elemente dauerhaft vom Gerät löschen\?</string>
<string name="batch_manga_save_confirm">Sind Sie sicher, dass Sie alle ausgewählten Mangas mit allen Kapiteln herunterladen möchten\? Diese Aktion kann eine Menge Datenverkehr und Speicherplatz verbrauchen</string>
<string name="batch_manga_save_confirm">Alle ausgewählten Mangas und ihre Kapitel herunterladen\? Das kann eine Menge Datenverkehr und Speicherplatz verbrauchen.</string>
<string name="removal_completed">Entfernung abgeschlossen</string>
<string name="download_slowdown">Download-Verzögerung</string>
<string name="parallel_downloads">Parallele Downloads</string>
@@ -312,10 +312,64 @@
<string name="show_reading_indicators_summary">Gelesenen Prozentsatz in Verlauf und Favoriten anzeigen</string>
<string name="clear_cookies_summary">Kann im Falle einiger Probleme helfen. Alle Berechtigungen werden für ungültig erklärt</string>
<string name="status_completed">Abgeschlossen</string>
<string name="exclude_nsfw_from_history_summary">Manga, die als NSFW markiert sind, werden nicht in den Verlauf aufgenommen und Ihr Fortschritt wird nicht gespeichert.</string>
<string name="exclude_nsfw_from_history_summary">Manga, die als NSFW markiert sind, werden nicht in den Verlauf aufgenommen und Ihr Fortschritt wird nicht gespeichert</string>
<string name="data_deletion">Datenlöschung</string>
<string name="invalid_domain_message">Ungültige Domäne</string>
<string name="status_reading">Lesen</string>
<string name="select_range">Bereich auswählen</string>
<string name="not_found_404">Inhalt nicht gefunden oder entfernt</string>
<string name="manga_error_description_pattern">Fehlerdetails:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Versuchen Sie, &lt;a href=%2$s&gt;den Manga in einem Webbrowser zu öffnen&lt;/a&gt;, um sicherzustellen, dass er in seiner Quelle verfügbar ist&lt;br&gt;2. Wenn er verfügbar ist, senden Sie einen Fehlerbericht an die Entwickler.</string>
<string name="no_bookmarks_yet">Noch keine Lesezeichen</string>
<string name="no_manga_sources">Keine Mangaquellen</string>
<string name="no_manga_sources_text">Manga-Quellen aktivieren, um Manga online zu lesen</string>
<string name="exit_confirmation">Bestätigung der Beendigung</string>
<string name="saved_manga">Gespeicherte Manga</string>
<string name="pages_cache">Seiten-Cache</string>
<string name="other_cache">Anderer Cache</string>
<string name="enter_email_text">Gib deine E-Mail-Adresse ein, um fortzufahren</string>
<string name="storage_usage">Speichernutzung</string>
<string name="available">Verfügbar</string>
<string name="memory_usage_pattern">%s - %s</string>
<string name="removed_from_favourites">Aus den Favoriten entfernt</string>
<string name="options">Optionen</string>
<string name="downloading_manga">Herunterladen von Manga</string>
<string name="automatic_scroll">Automatisches Blättern</string>
<string name="off_short">Aus</string>
<string name="seconds_pattern">%s s</string>
<string name="removed_from_s">Entfernt von „%s“</string>
<string name="comics_archive">Comics-Archiv</string>
<string name="reader_info_pattern">Ka. %1$d/%2$d Se. %3$d/%4$d</string>
<string name="text_shelf_holder_primary">Deine Manga werden hier angezeigt</string>
<string name="text_shelf_holder_secondary">Was du lesen sollst, findest du in der Rubrik „Erkunden“</string>
<string name="canceled">Abgebrochen</string>
<string name="account_already_exists">Konto existiert bereits</string>
<string name="back">Zurück</string>
<string name="sync">Synchronisierung</string>
<string name="sync_title">Synchronisiere deine Daten</string>
<string name="email_enter_hint">Gib deine E-Mail-Adresse ein, um fortzufahren</string>
<string name="clear_all_history">Gesamten Verlauf löschen</string>
<string name="last_2_hours">Letzte 2 Stunden</string>
<string name="history_cleared">Verlauf gelöscht</string>
<string name="manage">Verwalten</string>
<string name="no_bookmarks_summary">Du kannst beim Lesen von Mangas Lesezeichen anlegen</string>
<string name="bookmarks_removed">Lesezeichen entfernt</string>
<string name="random">Zufällig</string>
<string name="categories_delete_confirm">Bist du sicher, dass du die ausgewählten Lieblingskategorien löschen möchtest\?
\nAlle darin enthaltenen Manga gehen verloren, und dies kann nicht rückgängig gemacht werden.</string>
<string name="reorder">Neu anordnen</string>
<string name="empty">Leer</string>
<string name="changelog">Änderungsprotokoll</string>
<string name="explore">Erkunden</string>
<string name="confirm_exit">Drücke zum Verlassen erneut auf Zurück</string>
<string name="exit_confirmation_summary">Drücke zweimal auf Zurück, um die Anwendung zu beenden</string>
<string name="feed">Feed</string>
<string name="incognito_mode">Inkognito-Modus</string>
<string name="app_update_available_s">Aktualisierung der Anwendung verfügbar: %s</string>
<string name="no_chapters">Keine Kapitel</string>
<string name="reader_info_bar">Informationsleiste im Lesegerät anzeigen</string>
<string name="folder_with_images">Ordner mit Bildern</string>
<string name="importing_manga">Import von Manga</string>
<string name="import_completed">Import abgeschlossen</string>
<string name="import_completed_hint">Du kannst die Originaldatei aus dem Speicher löschen, um Platz zu sparen</string>
<string name="import_will_start_soon">Import wird bald beginnen</string>
</resources>

View File

@@ -386,4 +386,8 @@
<string name="reset">Restablecer</string>
<string name="text_unsaved_changes_prompt">Tienes cambios sin guardar. ¿Quieres guardarlos o descartarlos\?</string>
<string name="discard">Descartar</string>
<string name="error_no_space_left">Sin espacio en dispositivo</string>
<string name="webtoon_zoom">Zoom de webtoon</string>
<string name="webtoon_zoom_summary">Permitir el gesto de acercamiento/alejamiento en modo webtoon (beta)</string>
<string name="reader_slider">Mostrar el deslizador de cambio de página</string>
</resources>

View File

@@ -267,7 +267,7 @@
<string name="suggestions_excluded_genres_summary">Spécifiez les genres que vous ne voulez pas voir apparaître dans les suggestions</string>
<string name="text_delete_local_manga_batch">Supprimer définitivement les éléments sélectionnés de l\'appareil \?</string>
<string name="removal_completed">Suppression terminée</string>
<string name="batch_manga_save_confirm">Voulez-vous vraiment télécharger tous les mangas sélectionnés avec tous leurs chapitres \? Cette action peut consommer beaucoup de trafic et de stockage</string>
<string name="batch_manga_save_confirm">Télécharger tous les mangas sélectionnés et leurs chapitres \? Ceci peut consommer beaucoup de trafic et de stockage.</string>
<string name="parallel_downloads">Téléchargements parallèles</string>
<string name="download_slowdown">Ralentissement du téléchargement</string>
<string name="download_slowdown_summary">Permet d\'éviter le blocage de votre adresse IP</string>
@@ -374,7 +374,7 @@
<string name="import_completed">Importation terminée</string>
<string name="import_completed_hint">Vous pouvez supprimer le fichier original du stockage pour gagner de l\'espace</string>
<string name="import_will_start_soon">L\'importation va bientôt commencer</string>
<string name="text_unsaved_changes_prompt">Vous avez des modifications non sauvegardées, voulez-vous les sauvegarder ou les abandonner \?</string>
<string name="text_unsaved_changes_prompt">Sauvegarde ou abandon des modifications non sauvegardées \?</string>
<string name="discard">Abandonner</string>
<string name="history_shortcuts_summary">Rendre les mangas récents disponibles en appuyant longuement sur l\'icône de l\'application</string>
<string name="reader_control_ltr_summary">Taper sur le bord droit ou appuyer sur la touche droite permet toujours de passer à la page suivante</string>
@@ -385,4 +385,11 @@
<string name="contrast">Contraste</string>
<string name="reset">Réinitialiser</string>
<string name="color_correction_hint">Les paramètres de couleurs choisis seront sauvegardés pour ce manga</string>
<string name="error_no_space_left">Il n\'y a plus d\'espace sur l\'appareil</string>
<string name="reader_slider">Afficher le curseur de changement de page</string>
<string name="webtoon_zoom_summary">Autoriser les gestes de zoom avant/arrière en mode webtoon (bêta)</string>
<string name="webtoon_zoom">Zoom Webtoon</string>
<string name="network_unavailable_hint">Activez le Wi-Fi ou le réseau mobile pour lire les mangas en ligne</string>
<string name="different_languages">Différentes langues</string>
<string name="network_unavailable">Le réseau n\'est pas disponible</string>
</resources>

View File

@@ -267,7 +267,7 @@
<string name="suggestions_excluded_genres_summary">サジェストで表示したくないジャンルを指定</string>
<string name="text_delete_local_manga_batch">選択した項目をデバイスから完全に削除しますか?</string>
<string name="removal_completed">削除が完了しました</string>
<string name="batch_manga_save_confirm">本当に選択したマンガを全編ダウンロードしますか?この動作は多くのトラフィックとストレージを消費する可能性があります</string>
<string name="batch_manga_save_confirm">選択したマンガとそのチャプターをすべてダウンロードしますか?これは、多くのトラフィックとストレージを消費する可能性があります。</string>
<string name="download_slowdown_summary">IPアドレスのブロックを回避することができます</string>
<string name="local_manga_processing">保存されたマンガの処理</string>
<string name="download_slowdown">ダウンロードの速度低下</string>
@@ -383,7 +383,16 @@
<string name="brightness">輝度</string>
<string name="contrast">コントラスト</string>
<string name="reset">リセット</string>
<string name="text_unsaved_changes_prompt">未保存の変更がありますが、保存しますか、それとも破棄しますか?</string>
<string name="text_unsaved_changes_prompt">未保存の変更を保存または破棄しますか\?</string>
<string name="color_correction_hint">選択した色の設定は、この漫画のために記憶されます</string>
<string name="discard">破棄</string>
<string name="error_no_space_left">デバイスに空き容量がありません</string>
<string name="reader_slider">ページ切り替えスライダーを表示</string>
<string name="server_error">サーバーサイドエラー (%1$d) です。後で再試行してください</string>
<string name="clear_new_chapters_counters">新しいチャプターの情報も明確に</string>
<string name="different_languages">さまざまな言語</string>
<string name="network_unavailable">ネットワークが利用できません</string>
<string name="network_unavailable_hint">Wi-Fiまたはモバイルネットワークをオンにして、オンラインでマンガを読むことができます</string>
<string name="webtoon_zoom">Webtoonズーム</string>
<string name="webtoon_zoom_summary">ウェブトゥーンモードでズームイン/ズームアウトのジェスチャーを可能にする(ベータ版)</string>
</resources>

View File

@@ -277,4 +277,66 @@
<string name="removed_from_history">Fjernet fra historikk</string>
<string name="bookmark_remove">Fjern bokmerke</string>
<string name="send">Send</string>
<string name="suggestions_updating">Oppdaterer forslag …</string>
<string name="hide">Skjul</string>
<string name="new_sources_text">Nye manga-kilder tilgjengelig</string>
<string name="parallel_downloads">Parallelle nedlastinger</string>
<string name="download_slowdown">Tregere nedlasting</string>
<string name="email_enter_hint">Skriv inn e-postadressen din for å fortsette</string>
<string name="back">Tilbake</string>
<string name="sync">Synkronisering</string>
<string name="sync_title">Synkroniser dataen din</string>
<string name="explore">Utforsk</string>
<string name="off_short">Av</string>
<string name="import_completed">Importert</string>
<string name="enter_email_text">Skriv inn e-postadressen din for å fortsette</string>
<string name="changelog">Endringslogg</string>
<string name="no_chapters">Ingen kapitler</string>
<string name="automatic_scroll">Automatisk rulling</string>
<string name="confirm_exit">Trykk «Tilbake» igjen for å avslutte</string>
<string name="exit_confirmation_summary">Trykk «Tilbake» to ganger for å avslutte programmet</string>
<string name="removed_from_favourites">Fjernet fra favoritter</string>
<string name="removed_from_s">Fjernet fra «%s»</string>
<string name="options">Alternativer</string>
<string name="batch_manga_save_confirm">Last ned all valgt manga og tilhørende kapitler\? Dette kan bruke mye data og lagringsplass.</string>
<string name="text_shelf_holder_primary">Din manga vil vises her</string>
<string name="text_shelf_holder_secondary">Finn ting å lese i «Utforsk»-delen</string>
<string name="suggestions_excluded_genres">Utelat sjangere</string>
<string name="suggestions_excluded_genres_summary">Angi sjangere du ikke vil ha foreslått</string>
<string name="text_delete_local_manga_batch">Slett valgte elementer fra enheten for godt\?</string>
<string name="canceled">Avbrutt</string>
<string name="account_already_exists">Kontoen finnes allerede</string>
<string name="download_slowdown_summary">Kan unngå å få IP-adressen din blokkert</string>
<string name="tracking">Sporing</string>
<string name="logout">Logg ut</string>
<string name="status_completed">Fullført</string>
<string name="status_on_hold">På vent</string>
<string name="appwidget_shelf_description">Manga fra favorittene dine</string>
<string name="appwidget_recent_description">Manga du har lest nylig</string>
<string name="clear_all_history">Tøm all historikk</string>
<string name="last_2_hours">De siste to timene</string>
<string name="history_cleared">Historikk tømt</string>
<string name="manage">Håndter</string>
<string name="no_bookmarks_yet">Ingen bokmerker enda</string>
<string name="bookmarks_removed">Bokmerker fjernet</string>
<string name="no_manga_sources">Ingen manga-kilder</string>
<string name="random">Tilfeldig</string>
<string name="empty">Tom</string>
<string name="saved_manga">Lagret manga</string>
<string name="available">Tilgjengelig</string>
<string name="seconds_pattern">%s s</string>
<string name="discard">Forkast</string>
<string name="reset">Tilbakestill</string>
<string name="contrast">Kontrast</string>
<string name="brightness">Lysstyrke</string>
<string name="color_correction">Fargekorrigering</string>
<string name="text_unsaved_changes_prompt">Lagre eller forkast ulagrede endringer\?</string>
<string name="compact">Kompakt</string>
<string name="disable_all">Skru av alle</string>
<string name="use_fingerprint">Bruk fingeravtrykk hvis tilgjengelig</string>
<string name="network_unavailable_hint">Skru på Wi-Fi eller mobilnettverk for å lese manga på nett</string>
<string name="notifications_enable">Skru på merknader</string>
<string name="show_reading_indicators">Vis indikatorer for leseforløp</string>
<string name="data_deletion">Datasletting</string>
<string name="exit_confirmation">Avsluttingsbekreftelse</string>
</resources>

View File

@@ -3,28 +3,28 @@
<!-- From ThemeOverlay.Material3.DynamicColors.Dark -->
<style name="Theme.Kotatsu.Monet">
<!-- Color palettes -->
<item name="colorPrimary">@color/m3_sys_color_dynamic_dark_primary</item>
<item name="colorOnPrimary">@color/m3_sys_color_dynamic_dark_on_primary</item>
<item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_dark_inverse_primary</item>
<item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_dark_primary_container</item>
<item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_dark_on_primary_container</item>
<item name="colorSecondary">@color/m3_sys_color_dynamic_dark_secondary</item>
<item name="colorOnSecondary">@color/m3_sys_color_dynamic_dark_on_secondary</item>
<item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_dark_secondary_container</item>
<item name="colorOnSecondaryContainer">@color/m3_sys_color_dynamic_dark_on_secondary_container</item>
<item name="colorTertiary">@color/m3_sys_color_dynamic_dark_tertiary</item>
<item name="colorOnTertiary">@color/m3_sys_color_dynamic_dark_on_tertiary</item>
<item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_dark_tertiary_container</item>
<item name="colorOnTertiaryContainer">@color/m3_sys_color_dynamic_dark_on_tertiary_container</item>
<item name="android:colorBackground">@color/m3_sys_color_dynamic_dark_background</item>
<item name="colorOnBackground">@color/m3_sys_color_dynamic_dark_on_background</item>
<item name="colorSurface">@color/m3_sys_color_dynamic_dark_surface</item>
<item name="colorOnSurface">@color/m3_sys_color_dynamic_dark_on_surface</item>
<item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_dark_surface_variant</item>
<item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_dark_on_surface_variant</item>
<item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_surface</item>
<item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_on_surface</item>
<item name="colorOutline">@color/m3_sys_color_dynamic_dark_outline</item>
<item name="colorPrimary">@android:color/system_accent1_200</item>
<item name="colorOnPrimary">@android:color/system_accent1_900</item>
<item name="colorPrimaryInverse">@android:color/system_accent1_600</item>
<item name="colorPrimaryContainer">@android:color/system_accent1_700</item>
<item name="colorOnPrimaryContainer">@android:color/system_accent1_100</item>
<item name="colorSecondary">@android:color/system_accent1_200</item>
<item name="colorOnSecondary">@android:color/system_neutral1_1000</item>
<item name="colorSecondaryContainer">@android:color/system_accent2_800</item>
<item name="colorOnSecondaryContainer">@android:color/system_accent2_100</item>
<item name="colorTertiary">@android:color/system_accent3_200</item>
<item name="colorOnTertiary">@android:color/system_accent3_800</item>
<item name="colorTertiaryContainer">@android:color/system_accent3_700</item>
<item name="colorOnTertiaryContainer">@android:color/system_accent3_100</item>
<item name="android:colorBackground">@android:color/system_neutral1_900</item>
<item name="colorOnBackground">@android:color/system_neutral1_200</item>
<item name="colorSurface">@android:color/system_neutral1_900</item>
<item name="colorOnSurface">@android:color/system_neutral1_100</item>
<item name="colorSurfaceVariant">@android:color/system_neutral2_700</item>
<item name="colorOnSurfaceVariant">@android:color/system_neutral2_200</item>
<item name="colorSurfaceInverse">@android:color/system_neutral1_100</item>
<item name="colorOnSurfaceInverse">@android:color/system_neutral1_800</item>
<item name="colorOutline">@android:color/system_neutral2_500</item>
<!-- Default Framework Text Colors. -->
<item name="android:textColorPrimary">@color/m3_dynamic_dark_default_color_primary_text</item>

View File

@@ -8,5 +8,6 @@
<color name="scrollbar">#66FFFFFF</color>
<color name="selector_foreground">#29FFFFFF</color>
<color name="divider_default">#1FFFFFFF</color>
<color name="status_bar_incognito">#260052</color>
</resources>

View File

@@ -6,6 +6,7 @@
<resources>
<color name="kotatsu_primary">#A5C8FF</color>
<color name="kotatsu_onPrimary">#00315E</color>
<color name="kotatsu_inversePrimary">#005FAE</color>
<color name="kotatsu_primaryContainer">#004785</color>
<color name="kotatsu_onPrimaryContainer">#D4E3FF</color>
<color name="kotatsu_secondary">#BCC7DC</color>
@@ -16,20 +17,15 @@
<color name="kotatsu_onTertiary">#3D2946</color>
<color name="kotatsu_tertiaryContainer">#553F5E</color>
<color name="kotatsu_onTertiaryContainer">#F7D8FF</color>
<color name="kotatsu_error">#FFB4AB</color>
<color name="kotatsu_errorContainer">#93000A</color>
<color name="kotatsu_onError">#690005</color>
<color name="kotatsu_onErrorContainer">#FFDAD6</color>
<color name="kotatsu_background">#1A1C1E</color>
<color name="kotatsu_onBackground">#E3E2E6</color>
<color name="kotatsu_surface">#1A1C1E</color>
<color name="kotatsu_onSurface">#E3E2E6</color>
<color name="kotatsu_surfaceVariant">#43474E</color>
<color name="kotatsu_onSurfaceVariant">#C3C6CF</color>
<color name="kotatsu_outline">#8D9199</color>
<color name="kotatsu_inverseOnSurface">#1A1C1E</color>
<color name="kotatsu_inverseSurface">#E3E2E6</color>
<color name="kotatsu_inversePrimary">#005FAE</color>
<color name="kotatsu_inverseOnSurface">#1A1C1E</color>
<color name="kotatsu_outline">#8D9199</color>
<color name="kotatsu_shadow">#000000</color>
<color name="kotatsu_surfaceTint">#A5C8FF</color>
<color name="kotatsu_surfaceTintColor">#A5C8FF</color>

View File

@@ -266,7 +266,7 @@
<string name="suggestions_excluded_genres_summary">Укажите жанры, которые Вы не хотите видеть в рекомендациях</string>
<string name="text_delete_local_manga_batch">Удалить выбранную мангу с накопителя?</string>
<string name="removal_completed">Удаление завершено</string>
<string name="batch_manga_save_confirm">Загрузить выбранную мангу со всеми главами? Это может привести к большому расходу трафика и места на накопителе</string>
<string name="batch_manga_save_confirm">Загрузить выбранную мангу со всеми главами\? Это может привести к большому расходу трафика и места на накопителе.</string>
<string name="parallel_downloads">Загружать параллельно</string>
<string name="download_slowdown">Замедление загрузки</string>
<string name="download_slowdown_summary">Помогает избежать блокировки IP-адреса</string>
@@ -376,14 +376,24 @@
<string name="downloading_manga">Загрузка манги</string>
<string name="manga_error_description_pattern">Информация об ошибке:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Попробуйте &lt;a href=%2$s&gt;открыть мангу в веб-браузере&lt;/a&gt;, чтобы убедиться, что она доступна в источнике&lt;br&gt;2. Если она доступна, отправьте отчёт об ошибке разработчикам.</string>
<string name="history_shortcuts">Показывать ярлыки последней прочитанной манги</string>
<string name="history_shortcuts_summary">Сделать недавно прочитанную доступной по долгому нажатию на иконку приложения</string>
<string name="history_shortcuts_summary">Сделать недавно прочитанную мангу доступной по долгому нажатию на иконку приложения</string>
<string name="reader_control_ltr_summary">Нажатие на правый край или нажатие правой клавиши всегда переключает на следующую страницу</string>
<string name="reader_control_ltr">Эргономичное управление режимом чтения</string>
<string name="reset">Сбросить</string>
<string name="discard">Отменить</string>
<string name="text_unsaved_changes_prompt">У вас есть несохраненные изменения, вы хотите сохранить или отменить их\?</string>
<string name="text_unsaved_changes_prompt">Сохранить или отменить несохранённые изменения\?</string>
<string name="contrast">Контрастность</string>
<string name="brightness">Яркость</string>
<string name="color_correction">Цветокоррекция</string>
<string name="color_correction_hint">Выбранные настройки цвета запомнятся для этой манги</string>
<string name="color_correction_hint">Выбранные настройки цвета будут сохранены для этой манги</string>
<string name="error_no_space_left">Не осталось свободного места на накопителе</string>
<string name="webtoon_zoom">Масштабирование в режиме манхвы</string>
<string name="webtoon_zoom_summary">Позволяет масштабировать страницы в режиме манхвы (бета)</string>
<string name="reader_slider">Показывать слайдер переключения страниц</string>
<string name="network_unavailable_hint">Включите Wi-Fi или передачу данных, чтобы читать мангу онлайн</string>
<string name="different_languages">Разные языки</string>
<string name="network_unavailable">Сеть недоступна</string>
<string name="clear_new_chapters_counters">Также очистить информацию о новых главах</string>
<string name="server_error">Внутренняя ошибка сервера (%1$d). Повторите попытку позже</string>
<string name="compact">Компактно</string>
</resources>

View File

@@ -382,4 +382,8 @@
<string name="color_correction_hint">Seçilen renk ayarları bu manga için hatırlanacaktır</string>
<string name="text_unsaved_changes_prompt">Kaydedilmemiş değişiklikleriniz var, kaydetmek mi istersiniz yoksa, yoksaymak mı istersiniz\?</string>
<string name="discard">Yoksay</string>
<string name="error_no_space_left">Cihazda yer yok</string>
<string name="webtoon_zoom">Webtoon yakınlaştırma</string>
<string name="webtoon_zoom_summary">Webtoon modunda yakınlaştırma hareketine izin ver (beta)</string>
<string name="reader_slider">Sayfa değiştirme kaydırıcısını göster</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

@@ -211,7 +211,7 @@
<string name="suggestions_updating">Оновлення пропозицій</string>
<string name="text_delete_local_manga_batch">Видалити вибрані елементи з пристрою назавжди\?</string>
<string name="removal_completed">Видалення завершено</string>
<string name="batch_manga_save_confirm">Ви впевнені, що хочете завантажити всю вибрану манґу з усіма її розділами\? Це може споживати багато трафіку та пам’яті</string>
<string name="batch_manga_save_confirm">Завантажити всю вибрану манґу з усіма її розділами\? Це може споживати багато трафіку та пам’яті.</string>
<string name="parallel_downloads">Завантажувати паралельно</string>
<string name="download_slowdown">Сповільнення завантаження</string>
<string name="local_manga_processing">Обробка збереженої манґи</string>
@@ -292,7 +292,7 @@
<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_summary">Автоматично визначати, чи є манґа вебтуном</string>
<string name="detect_reader_mode">Автовизначення режиму читання</string>
<string name="disable_battery_optimization">Вимкнути оптимізацію акумулятора</string>
<string name="disable_battery_optimization_summary">Допомагає з перевірками фонових оновлень</string>
@@ -383,6 +383,15 @@
<string name="contrast">Контрастність</string>
<string name="reset">Скинути</string>
<string name="color_correction_hint">Вибрані параметри кольору будуть запам\'ятовані для цієї манґи</string>
<string name="text_unsaved_changes_prompt">У вас є незбережені зміни, ви хочете зберегти чи скасувати їх\?</string>
<string name="text_unsaved_changes_prompt">Зберегти чи скасувати незбережені зміни\?</string>
<string name="discard">Скасувати</string>
<string name="server_error">Помилка на стороні сервера (%1$d). Будь ласка спробуйте пізніше</string>
<string name="clear_new_chapters_counters">Також очистити інформацію про нові розділи</string>
<string name="error_no_space_left">На пристрої не залишилося вільного місця</string>
<string name="different_languages">Різні мови</string>
<string name="network_unavailable">Мережа недоступна</string>
<string name="network_unavailable_hint">Увімкніть Wi-Fi або мобільну мережу, щоб читати манґу онлайн</string>
<string name="webtoon_zoom">Масштабування в режимі вебтуну</string>
<string name="webtoon_zoom_summary">Дозволити жести збільшення/зменшення масштабу в режимі вебтуну (бета)</string>
<string name="reader_slider">Відображати повзунок перемикання сторінок</string>
</resources>

View File

@@ -3,28 +3,28 @@
<!-- From ThemeOverlay.Material3.DynamicColors.Light -->
<style name="Theme.Kotatsu.Monet">
<!-- Color palettes -->
<item name="colorPrimary">@color/m3_sys_color_dynamic_light_primary</item>
<item name="colorOnPrimary">@color/m3_sys_color_dynamic_light_on_primary</item>
<item name="colorPrimaryInverse">@color/m3_sys_color_dynamic_light_inverse_primary</item>
<item name="colorPrimaryContainer">@color/m3_sys_color_dynamic_light_primary_container</item>
<item name="colorOnPrimaryContainer">@color/m3_sys_color_dynamic_light_on_primary_container</item>
<item name="colorSecondary">@color/m3_sys_color_dynamic_light_secondary</item>
<item name="colorOnSecondary">@color/m3_sys_color_dynamic_light_on_secondary</item>
<item name="colorSecondaryContainer">@color/m3_sys_color_dynamic_light_secondary_container</item>
<item name="colorOnSecondaryContainer">@color/m3_sys_color_dynamic_light_on_secondary_container</item>
<item name="colorTertiary">@color/m3_sys_color_dynamic_light_tertiary</item>
<item name="colorOnTertiary">@color/m3_sys_color_dynamic_light_on_tertiary</item>
<item name="colorTertiaryContainer">@color/m3_sys_color_dynamic_light_tertiary_container</item>
<item name="colorOnTertiaryContainer">@color/m3_sys_color_dynamic_light_on_tertiary_container</item>
<item name="android:colorBackground">@color/m3_sys_color_dynamic_light_background</item>
<item name="colorOnBackground">@color/m3_sys_color_dynamic_light_on_background</item>
<item name="colorSurface">@color/m3_sys_color_dynamic_light_surface</item>
<item name="colorOnSurface">@color/m3_sys_color_dynamic_light_on_surface</item>
<item name="colorSurfaceVariant">@color/m3_sys_color_dynamic_light_surface_variant</item>
<item name="colorOnSurfaceVariant">@color/m3_sys_color_dynamic_light_on_surface_variant</item>
<item name="colorSurfaceInverse">@color/m3_sys_color_dynamic_light_inverse_surface</item>
<item name="colorOnSurfaceInverse">@color/m3_sys_color_dynamic_light_inverse_on_surface</item>
<item name="colorOutline">@color/m3_sys_color_dynamic_light_outline</item>
<item name="colorPrimary">@android:color/system_accent1_500</item>
<item name="colorOnPrimary">@android:color/system_accent1_0</item>
<item name="colorPrimaryInverse">@android:color/system_accent1_200</item>
<item name="colorPrimaryContainer">@android:color/system_accent1_100</item>
<item name="colorOnPrimaryContainer">@android:color/system_accent1_900</item>
<item name="colorSecondary">@android:color/system_neutral1_600</item>
<item name="colorOnSecondary">@android:color/system_neutral1_0</item>
<item name="colorSecondaryContainer">@android:color/system_accent2_100</item>
<item name="colorOnSecondaryContainer">@android:color/system_accent1_900</item>
<item name="colorTertiary">@android:color/system_accent3_500</item>
<item name="colorOnTertiary">@android:color/system_accent3_50</item>
<item name="colorTertiaryContainer">@android:color/system_accent3_100</item>
<item name="colorOnTertiaryContainer">@android:color/system_accent3_900</item>
<item name="android:colorBackground">@android:color/system_neutral1_50</item>
<item name="colorOnBackground">@android:color/system_neutral1_900</item>
<item name="colorSurface">@android:color/system_neutral1_50</item>
<item name="colorOnSurface">@android:color/system_neutral1_900</item>
<item name="colorSurfaceVariant">@android:color/system_neutral2_100</item>
<item name="colorOnSurfaceVariant">@android:color/system_neutral2_700</item>
<item name="colorSurfaceInverse">@android:color/system_neutral1_800</item>
<item name="colorOnSurfaceInverse">@android:color/system_neutral1_50</item>
<item name="colorOutline">@android:color/system_neutral2_500</item>
<!-- Default Framework Text Colors. -->
<item name="android:textColorPrimary">@color/m3_dynamic_default_color_primary_text</item>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="new_chapters">
<item quantity="other">%1$d chương mới</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d chương</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="other">%1$d phút trước</item>
</plurals>
<plurals name="hours_ago">
<item quantity="other">%1$d giờ trước</item>
</plurals>
<plurals name="days_ago">
<item quantity="other">%1$d ngày trước</item>
</plurals>
</resources>

View File

@@ -269,7 +269,7 @@
<string name="removal_completed">删除已完成</string>
<string name="parallel_downloads">并行下载</string>
<string name="download_slowdown">下载速度减慢</string>
<string name="batch_manga_save_confirm">你确定你要下载所有选定的漫画及其所有章节吗\? 这个动作会消耗大量的流量和存储空间</string>
<string name="batch_manga_save_confirm">下载所有选定的漫画及其所有章节吗\? 这会消耗大量的流量和存储空间。</string>
<string name="download_slowdown_summary">有助于避免阻断你的IP地址</string>
<string name="local_manga_processing">保存的漫画处理</string>
<string name="chapters_will_removed_background">章节将在后台被删除. 这可能需要一些时间</string>
@@ -383,6 +383,16 @@
<string name="brightness">亮度</string>
<string name="contrast">对比度</string>
<string name="color_correction_hint">所选颜色设置将会应用于此漫画</string>
<string name="text_unsaved_changes_prompt">您有未保存的更改,您希望存储还是放弃更改?</string>
<string name="text_unsaved_changes_prompt">保存还是放弃未保存的更改?</string>
<string name="discard">放弃</string>
<string name="error_no_space_left">设备上没有剩余空间</string>
<string name="reader_slider">显示换页滑块</string>
<string name="webtoon_zoom">Webtoon 缩放</string>
<string name="webtoon_zoom_summary">在 webtoon 模式下允许缩小/放大(测试)</string>
<string name="different_languages">不同语言</string>
<string name="network_unavailable">网络不可用</string>
<string name="network_unavailable_hint">打开 Wi-Fi 或移动网络在线阅读漫画</string>
<string name="clear_new_chapters_counters">同样清除新章节信息</string>
<string name="server_error">服务器端错误 (%1$d)。请稍后再试</string>
<string name="compact">紧凑</string>
</resources>

View File

@@ -21,5 +21,5 @@
<color name="scrollbar">#66000000</color>
<color name="selector_foreground">#29000000</color>
<color name="divider_default">#1F000000</color>
<color name="status_bar_incognito">#334800E0</color>
</resources>

View File

@@ -6,6 +6,7 @@
<resources>
<color name="kotatsu_primary">#005FAE</color>
<color name="kotatsu_onPrimary">#FFFFFF</color>
<color name="kotatsu_inversePrimary">#A5C8FF</color>
<color name="kotatsu_primaryContainer">#D4E3FF</color>
<color name="kotatsu_onPrimaryContainer">#001C3A</color>
<color name="kotatsu_secondary">#545F71</color>
@@ -16,16 +17,15 @@
<color name="kotatsu_onTertiary">#FFFFFF</color>
<color name="kotatsu_tertiaryContainer">#F7D8FF</color>
<color name="kotatsu_onTertiaryContainer">#271430</color>
<color name="kotatsu_background">#FDFCFF</color>
<color name="kotatsu_background">#F0F0F3</color>
<color name="kotatsu_onBackground">#1A1C1E</color>
<color name="kotatsu_surface">#FDFCFF</color>
<color name="kotatsu_surface">#F0F0F3</color>
<color name="kotatsu_onSurface">#1A1C1E</color>
<color name="kotatsu_surfaceVariant">#E0E2EC</color>
<color name="kotatsu_onSurfaceVariant">#43474E</color>
<color name="kotatsu_outline">#74777F</color>
<color name="kotatsu_inverseOnSurface">#F1F0F4</color>
<color name="kotatsu_inverseSurface">#2F3033</color>
<color name="kotatsu_inversePrimary">#A5C8FF</color>
<color name="kotatsu_inverseOnSurface">#F1F0F4</color>
<color name="kotatsu_outline">#74777F</color>
<color name="kotatsu_shadow">#000000</color>
<color name="kotatsu_surfaceTint">#005FAE</color>
<color name="kotatsu_surfaceTintColor">#005FAE</color>

View File

@@ -271,7 +271,7 @@
<string name="suggestions_excluded_genres_summary">Specify genres that you do not want to see in the suggestions</string>
<string name="text_delete_local_manga_batch">Delete selected items from device permanently?</string>
<string name="removal_completed">Removal completed</string>
<string name="batch_manga_save_confirm">Are you sure you want to download all selected manga with all its chapters? This action can consume a lot of traffic and storage</string>
<string name="batch_manga_save_confirm">Download all selected manga and its chapters\? This can consume a lot of traffic and storage.</string>
<string name="shikimori" translatable="false">Shikimori</string>
<string name="parallel_downloads">Parallel downloads</string>
<string name="download_slowdown">Download slowdown</string>
@@ -295,7 +295,7 @@
<string name="edit_category">Edit category</string>
<string name="tracking">Tracking</string>
<string name="empty_favourite_categories">No favourite categories</string>
<string name="logout">Logout</string>
<string name="logout">Log out</string>
<string name="bookmark_add">Add bookmark</string>
<string name="bookmark_remove">Remove bookmark</string>
<string name="bookmarks">Bookmarks</string>
@@ -360,7 +360,7 @@
<string name="options">Options</string>
<string name="not_found_404">Content not found or removed</string>
<string name="downloading_manga">Downloading manga</string>
<string name="download_summary_pattern" translatable="false">&lt;b>%1$s&lt;/b> %2$s</string>
<string name="download_summary_pattern" translatable="false">&lt;b&gt;%1$s&lt;/b&gt; %2$s</string>
<string name="incognito_mode">Incognito mode</string>
<string name="app_update_available_s">Application update available: %s</string>
<string name="no_chapters">No chapters</string>
@@ -376,7 +376,7 @@
<string name="import_completed_hint">You can delete the original file from storage to save space</string>
<string name="import_will_start_soon">Import will start soon</string>
<string name="feed">Feed</string>
<string name="manga_error_description_pattern">Error details:&lt;br>&lt;tt>%1$s&lt;/tt>&lt;br>&lt;br>1. Try to &lt;a href="%2$s">open manga in a web browser&lt;/a> to ensure it is available on its source&lt;br>2. If it is available, send an error report to the developers.</string>
<string name="manga_error_description_pattern">Error details:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. Try to &lt;a href="%2$s"&gt;open manga in a web browser&lt;/a&gt; to ensure it is available on its source&lt;br&gt;2. If it is available, send an error report to the developers.</string>
<string name="history_shortcuts">Show recent manga shortcuts</string>
<string name="history_shortcuts_summary">Make recent manga available by long pressing on application icon</string>
<string name="reader_control_ltr_summary">Tap on the right edge or pressing the right key always switches to the next page</string>
@@ -386,8 +386,16 @@
<string name="contrast">Contrast</string>
<string name="reset">Reset</string>
<string name="color_correction_hint">The chosen color settings will be remembered for this manga</string>
<string name="text_unsaved_changes_prompt">You have unsaved changes, do you want to save or discard them?</string>
<string name="text_unsaved_changes_prompt">Save or discard unsaved changes\?</string>
<string name="discard">Discard</string>
<string name="error_no_space_left">No space left on device</string>
<string name="reader_slider">Show page switching slider</string>
<string name="webtoon_zoom">Webtoon zoom</string>
<string name="webtoon_zoom_summary">Allow zoom in/zoom out gesture in webtoon mode (beta)</string>
<string name="different_languages">Different languages</string>
<string name="network_unavailable">Network is not available</string>
<string name="network_unavailable_hint">Turn on Wi-Fi or mobile network to read manga online</string>
<string name="server_error">Server side error (%1$d). Please try again later</string>
<string name="clear_new_chapters_counters">Also clear information about new chapters</string>
<string name="compact">Compact</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More