Compare commits
81 Commits
v4.0-beta2
...
v4.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cec19c3db3 | ||
|
|
ff58539e2e | ||
|
|
d8e7689a94 | ||
|
|
32cfbb327c | ||
|
|
245e87256b | ||
|
|
ed8c69037f | ||
|
|
3f76d22d67 | ||
|
|
980988e684 | ||
|
|
347811abb6 | ||
|
|
ccb8b0c8e7 | ||
|
|
18137ab48e | ||
|
|
a9f435ae3d | ||
|
|
0758cfef64 | ||
|
|
06d1d56448 | ||
|
|
07c70eaccc | ||
|
|
5ad6413952 | ||
|
|
0b9d9ac7f2 | ||
|
|
3ab87027ab | ||
|
|
7545a774ba | ||
|
|
db16eb8e29 | ||
|
|
e5a27a7c6f | ||
|
|
d26bc102d1 | ||
|
|
fc6a8afd93 | ||
|
|
5a9d401446 | ||
|
|
77ac40b445 | ||
|
|
a29454f672 | ||
|
|
80ee7c8e54 | ||
|
|
fb202f80a5 | ||
|
|
45dbd5aa44 | ||
|
|
ee65251bf5 | ||
|
|
eaeb11f9ce | ||
|
|
2f74633abb | ||
|
|
0f346dc725 | ||
|
|
1b92848964 | ||
|
|
3d91583585 | ||
|
|
f76d9fa3e4 | ||
|
|
b00b2e406e | ||
|
|
74717e2b93 | ||
|
|
9b54ed6bc7 | ||
|
|
7b36c64b34 | ||
|
|
da09884136 | ||
|
|
64aaf37556 | ||
|
|
11104223eb | ||
|
|
0c119bc137 | ||
|
|
5c058e626b | ||
|
|
2005ae2bf3 | ||
|
|
d0650c7cf4 | ||
|
|
2df4e6480a | ||
|
|
017a1686dc | ||
|
|
279dc03695 | ||
|
|
c8c482f692 | ||
|
|
02a0e3ebcd | ||
|
|
fc0b3f3b38 | ||
|
|
2925900214 | ||
|
|
eae370e41c | ||
|
|
d0338a604a | ||
|
|
e22b98b476 | ||
|
|
4d838d290d | ||
|
|
048efdf59f | ||
|
|
65dbc6b8e5 | ||
|
|
627a00beb4 | ||
|
|
e00ed13ad1 | ||
|
|
af2adeba13 | ||
|
|
893fa6bd90 | ||
|
|
512188c8dd | ||
|
|
aae6761809 | ||
|
|
c3f055d0c4 | ||
|
|
93c6bec452 | ||
|
|
04d5df20d1 | ||
|
|
665eca0699 | ||
|
|
9a1534464f | ||
|
|
f856fc6fac | ||
|
|
4af8e73303 | ||
|
|
23239f1fec | ||
|
|
853e4d6fde | ||
|
|
14a37ad16e | ||
|
|
c944044465 | ||
|
|
8a63ca2310 | ||
|
|
12e5e3b35e | ||
|
|
553a85ef86 | ||
|
|
de7012cabf |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
6
.idea/compiler.xml
generated
@@ -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
2
.idea/kotlinc.xml
generated
@@ -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>
|
||||
@@ -15,8 +15,8 @@ android {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 498
|
||||
versionName '4.0-beta2'
|
||||
versionCode 502
|
||||
versionName '4.0.2'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
@@ -60,6 +60,7 @@ android {
|
||||
'-opt-in=kotlinx.coroutines.FlowPreview',
|
||||
'-opt-in=kotlin.contracts.ExperimentalContracts',
|
||||
'-opt-in=coil.annotation.ExperimentalCoilApi',
|
||||
'-opt-in=com.google.android.material.badge.ExperimentalBadgeUtils',
|
||||
]
|
||||
}
|
||||
lint {
|
||||
@@ -82,15 +83,15 @@ afterEvaluate {
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:b3a9c5fcda') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:a1441e7ed7') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.5.1'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.2'
|
||||
implementation 'androidx.activity:activity-ktx:1.6.0'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.5.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-service:2.5.1'
|
||||
@@ -101,8 +102,8 @@ dependencies {
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
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-alpha04'
|
||||
implementation 'com.google.android.material:material:1.7.0-rc01'
|
||||
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
//noinspection LifecycleAnnotationProcessorWithJava8
|
||||
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
|
||||
|
||||
@@ -117,14 +118,14 @@ dependencies {
|
||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
|
||||
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
|
||||
|
||||
implementation "com.google.dagger:hilt-android:2.43.2"
|
||||
kapt "com.google.dagger:hilt-compiler:2.43.2"
|
||||
implementation "com.google.dagger:hilt-android:2.44"
|
||||
kapt "com.google.dagger:hilt-compiler:2.44"
|
||||
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 'com.github.KotatsuApp:subsampling-scale-image-view:0ff0278f0f'
|
||||
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:f8a38b08fe'
|
||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||
|
||||
implementation 'ch.acra:acra-http:5.9.6'
|
||||
@@ -133,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'
|
||||
@@ -146,6 +147,6 @@ dependencies {
|
||||
androidTestImplementation 'androidx.room:room-testing:2.4.3'
|
||||
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
|
||||
|
||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.43.2'
|
||||
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.43.2'
|
||||
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44'
|
||||
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.44'
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -4,11 +4,13 @@ import android.app.Service
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
|
||||
abstract class CoroutineIntentService : BaseService() {
|
||||
|
||||
@@ -21,11 +23,13 @@ abstract class CoroutineIntentService : BaseService() {
|
||||
return Service.START_REDELIVER_INTENT
|
||||
}
|
||||
|
||||
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch {
|
||||
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch(errorHandler(startId)) {
|
||||
mutex.withLock {
|
||||
try {
|
||||
withContext(dispatcher) {
|
||||
processIntent(intent)
|
||||
if (intent != null) {
|
||||
withContext(dispatcher) {
|
||||
processIntent(startId, intent)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
stopSelf(startId)
|
||||
@@ -33,5 +37,12 @@ abstract class CoroutineIntentService : BaseService() {
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract suspend fun processIntent(intent: Intent?)
|
||||
protected abstract suspend fun processIntent(startId: Int, intent: Intent)
|
||||
|
||||
protected abstract fun onError(startId: Int, error: Throwable)
|
||||
|
||||
private fun errorHandler(startId: Int) = CoroutineExceptionHandler { _, throwable ->
|
||||
throwable.printStackTraceDebug()
|
||||
onError(startId, throwable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,6 @@ import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.network.DoHProvider
|
||||
@@ -26,6 +20,14 @@ import org.koitharu.kotatsu.utils.ext.getEnumValue
|
||||
import org.koitharu.kotatsu.utils.ext.observe
|
||||
import org.koitharu.kotatsu.utils.ext.putEnumValue
|
||||
import org.koitharu.kotatsu.utils.ext.toUriOrNull
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Collections
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
@@ -206,6 +208,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
val isReaderBarEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_READER_BAR, true)
|
||||
|
||||
val isReaderSliderEnabled: Boolean
|
||||
get() = prefs.getBoolean(KEY_READER_SLIDER, true)
|
||||
|
||||
val dnsOverHttps: DoHProvider
|
||||
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
|
||||
|
||||
@@ -213,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
|
||||
@@ -328,9 +336,11 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_INCOGNITO_MODE = "incognito"
|
||||
const val KEY_SYNC = "sync"
|
||||
const val KEY_READER_BAR = "reader_bar"
|
||||
const val KEY_READER_SLIDER = "reader_slider"
|
||||
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"
|
||||
|
||||
@@ -54,7 +54,7 @@ class MangaErrorDialog : AlertDialogFragment<DialogMangaErrorBinding>() {
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(R.string.report) { _, _ ->
|
||||
dismiss()
|
||||
error.report(TAG)
|
||||
error.report()
|
||||
}.setTitle(R.string.error_occurred)
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,10 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
@@ -37,12 +35,17 @@ import org.koitharu.kotatsu.core.ui.MangaErrorDialog
|
||||
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
||||
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.list.ui.adapter.bindBadge
|
||||
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import org.koitharu.kotatsu.utils.ViewBadge
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.isReportable
|
||||
import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat
|
||||
import org.koitharu.kotatsu.utils.ext.textAndVisible
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class DetailsActivity :
|
||||
@@ -60,7 +63,7 @@ class DetailsActivity :
|
||||
@Inject
|
||||
lateinit var shortcutsUpdater: ShortcutsUpdater
|
||||
|
||||
private var badge: BadgeDrawable? = null
|
||||
private lateinit var viewBadge: ViewBadge
|
||||
|
||||
private val viewModel: DetailsViewModel by assistedViewModels {
|
||||
viewModelFactory.create(MangaIntent(intent))
|
||||
@@ -83,6 +86,7 @@ class DetailsActivity :
|
||||
}
|
||||
binding.buttonRead.setOnClickListener(this)
|
||||
binding.buttonDropdown.setOnClickListener(this)
|
||||
viewBadge = ViewBadge(binding.buttonRead, this)
|
||||
|
||||
chaptersMenuProvider = if (binding.layoutBottom != null) {
|
||||
val bsMediator = ChaptersBottomSheetMediator(checkNotNull(binding.layoutBottom))
|
||||
@@ -151,6 +155,7 @@ class DetailsActivity :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
R.id.button_dropdown -> showBranchPopupMenu()
|
||||
}
|
||||
}
|
||||
@@ -188,10 +193,12 @@ class DetailsActivity :
|
||||
ExceptionResolver.canResolve(e) -> {
|
||||
resolveError(e)
|
||||
}
|
||||
|
||||
manga == null -> {
|
||||
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
|
||||
finishAfterTransition()
|
||||
}
|
||||
|
||||
else -> {
|
||||
val snackbar = makeSnackbar(
|
||||
e.getDisplayMessage(resources),
|
||||
@@ -242,7 +249,7 @@ class DetailsActivity :
|
||||
}
|
||||
|
||||
private fun onNewChaptersChanged(newChapters: Int) {
|
||||
badge = binding.buttonRead.bindBadge(badge, newChapters)
|
||||
viewBadge.counter = newChapters
|
||||
}
|
||||
|
||||
fun showChapterMissingDialog(chapterId: Long) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
|
||||
setContentView(ActivityDownloadsBinding.inflate(layoutInflater))
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
val adapter = DownloadsAdapter(lifecycleScope, coil)
|
||||
val spacing = resources.getDimensionPixelOffset(R.dimen.grid_spacing)
|
||||
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
|
||||
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.adapter = adapter
|
||||
|
||||
@@ -16,7 +16,12 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.transformWhile
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -85,7 +90,9 @@ class DownloadService : BaseService() {
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterReceiver(controlReceiver)
|
||||
wakeLock.release()
|
||||
if (wakeLock.isHeld) {
|
||||
wakeLock.release()
|
||||
}
|
||||
isRunning = false
|
||||
super.onDestroy()
|
||||
}
|
||||
@@ -169,6 +176,7 @@ class DownloadService : BaseService() {
|
||||
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
|
||||
jobs[cancelId]?.cancel()
|
||||
}
|
||||
|
||||
ACTION_DOWNLOAD_RESUME -> {
|
||||
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
|
||||
jobs[cancelId]?.resume()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
|
||||
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
|
||||
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
|
||||
import kotlin.text.Typography.dagger
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
@@ -28,6 +29,7 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
|
||||
val fm = supportFragmentManager
|
||||
if (fm.findFragmentById(R.id.container) == null) {
|
||||
fm.commit {
|
||||
setReorderingAllowed(true)
|
||||
val fragment = FavouritesListFragment.newInstance(intent.getLongExtra(EXTRA_CATEGORY_ID, NO_ID))
|
||||
replace(R.id.container, fragment)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ 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
|
||||
import kotlin.text.Typography.dagger
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HistoryActivity :
|
||||
@@ -28,6 +29,7 @@ class HistoryActivity :
|
||||
val fm = supportFragmentManager
|
||||
if (fm.findFragmentById(R.id.container) == null) {
|
||||
fm.commit {
|
||||
setReorderingAllowed(true)
|
||||
val fragment = HistoryListFragment.newInstance()
|
||||
replace(R.id.container, fragment)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
@file:SuppressLint("UnsafeOptInUsageError")
|
||||
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.View
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.core.view.doOnNextLayout
|
||||
@@ -42,4 +39,4 @@ private fun initBadge(anchor: View): BadgeDrawable {
|
||||
private fun BadgeDrawable.align() {
|
||||
horizontalOffset = intrinsicWidth
|
||||
verticalOffset = intrinsicHeight
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -23,7 +23,12 @@ import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.local.domain.importer.MangaImporter
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.PendingIntentCompat
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import org.koitharu.kotatsu.utils.ext.asArrayList
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
import org.koitharu.kotatsu.utils.ext.report
|
||||
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -48,8 +53,8 @@ class ImportService : CoroutineIntentService() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override suspend fun processIntent(intent: Intent?) {
|
||||
val uris = intent?.getParcelableArrayListExtra<Uri>(EXTRA_URIS)
|
||||
override suspend fun processIntent(startId: Int, intent: Intent) {
|
||||
val uris = intent.getParcelableArrayListExtra<Uri>(EXTRA_URIS)
|
||||
if (uris.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
@@ -69,6 +74,10 @@ class ImportService : CoroutineIntentService() {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
|
||||
override fun onError(startId: Int, error: Throwable) {
|
||||
error.report()
|
||||
}
|
||||
|
||||
private suspend fun importImpl(uri: Uri): Manga {
|
||||
val importer = importerFactory.create(uri)
|
||||
return importer.import(uri)
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.download.ui.service.DownloadService
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -34,8 +35,8 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override suspend fun processIntent(intent: Intent?) {
|
||||
val manga = intent?.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
|
||||
override suspend fun processIntent(startId: Int, intent: Intent) {
|
||||
val manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
|
||||
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
|
||||
startForeground()
|
||||
val mangaWithChapters = localMangaRepository.getDetails(manga)
|
||||
@@ -47,6 +48,21 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
}
|
||||
|
||||
override fun onError(startId: Int, error: Throwable) {
|
||||
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.error_occurred))
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setDefaults(0)
|
||||
.setColor(ContextCompat.getColor(this, R.color.blue_primary_dark))
|
||||
.setSilent(true)
|
||||
.setContentText(error.getDisplayMessage(resources))
|
||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
nm.notify(NOTIFICATION_ID + startId, notification)
|
||||
}
|
||||
|
||||
private fun startForeground() {
|
||||
val title = getString(R.string.local_manga_processing)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
||||
@@ -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,10 +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 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"
|
||||
|
||||
@@ -115,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) {
|
||||
@@ -127,20 +141,18 @@ class MainActivity :
|
||||
binding.searchView.clearFocus()
|
||||
when {
|
||||
fragment != null -> supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
remove(fragment)
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
runOnCommit { onSearchClosed() }
|
||||
}
|
||||
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -173,6 +185,7 @@ class MainActivity :
|
||||
if (v?.id == R.id.searchView && hasFocus) {
|
||||
if (fragment == null) {
|
||||
supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
add(R.id.container, SearchSuggestionFragment.newInstance(), TAG_SEARCH)
|
||||
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
runOnCommit { onSearchOpened() }
|
||||
@@ -257,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
|
||||
}
|
||||
@@ -308,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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -85,6 +94,7 @@ class MainNavigationDelegate(
|
||||
|
||||
private fun setPrimaryFragment(fragment: Fragment) {
|
||||
fragmentManager.beginTransaction()
|
||||
.setReorderingAllowed(true)
|
||||
.replace(R.id.container, fragment, TAG_PRIMARY)
|
||||
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
|
||||
.commit()
|
||||
@@ -95,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)
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -49,7 +49,10 @@ class ProtectActivity :
|
||||
startActivity(intent)
|
||||
finishAfterTransition()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (!useFingerprint()) {
|
||||
binding.editPassword.requestFocus()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,23 @@ import android.transition.Fade
|
||||
import android.transition.Slide
|
||||
import android.transition.TransitionManager
|
||||
import android.transition.TransitionSet
|
||||
import android.view.*
|
||||
import android.view.Gravity
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.*
|
||||
import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -42,7 +50,17 @@ import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.utils.GridTouchHelper
|
||||
import org.koitharu.kotatsu.utils.IdlingDetector
|
||||
import org.koitharu.kotatsu.utils.ShareHelper
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
|
||||
import org.koitharu.kotatsu.utils.ext.hasGlobalPoint
|
||||
import org.koitharu.kotatsu.utils.ext.isReportable
|
||||
import org.koitharu.kotatsu.utils.ext.observeWithPrevious
|
||||
import org.koitharu.kotatsu.utils.ext.postDelayed
|
||||
import org.koitharu.kotatsu.utils.ext.report
|
||||
import org.koitharu.kotatsu.utils.ext.setValueRounded
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ReaderActivity :
|
||||
@@ -145,6 +163,7 @@ class ReaderActivity :
|
||||
R.id.action_settings -> {
|
||||
startActivity(SettingsActivity.newReaderSettingsIntent(this))
|
||||
}
|
||||
|
||||
R.id.action_chapters -> {
|
||||
ChaptersBottomSheet.show(
|
||||
supportFragmentManager,
|
||||
@@ -152,6 +171,7 @@ class ReaderActivity :
|
||||
viewModel.getCurrentState()?.chapterId ?: 0L,
|
||||
)
|
||||
}
|
||||
|
||||
R.id.action_pages_thumbs -> {
|
||||
val pages = viewModel.getCurrentChapterPages()
|
||||
if (!pages.isNullOrEmpty()) {
|
||||
@@ -165,6 +185,7 @@ class ReaderActivity :
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
R.id.action_bookmark -> {
|
||||
if (viewModel.isBookmarkAdded.value == true) {
|
||||
viewModel.removeBookmark()
|
||||
@@ -172,11 +193,13 @@ class ReaderActivity :
|
||||
viewModel.addBookmark()
|
||||
}
|
||||
}
|
||||
|
||||
R.id.action_options -> {
|
||||
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
|
||||
val currentMode = readerManager.currentMode ?: return false
|
||||
ReaderConfigBottomSheet.show(supportFragmentManager, currentMode)
|
||||
}
|
||||
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
return true
|
||||
@@ -364,9 +387,9 @@ class ReaderActivity :
|
||||
binding.toastView.showTemporary(uiState.chapterName, TOAST_DURATION)
|
||||
}
|
||||
}
|
||||
if (uiState.totalPages > 1 && uiState.currentPage < uiState.totalPages) {
|
||||
if (uiState.isSliderAvailable()) {
|
||||
binding.slider.valueTo = uiState.totalPages.toFloat() - 1
|
||||
binding.slider.value = uiState.currentPage.toFloat()
|
||||
binding.slider.setValueRounded(uiState.currentPage.toFloat())
|
||||
binding.slider.isVisible = true
|
||||
} else {
|
||||
binding.slider.isVisible = false
|
||||
@@ -383,7 +406,7 @@ class ReaderActivity :
|
||||
if (ExceptionResolver.canResolve(exception)) {
|
||||
tryResolve(exception)
|
||||
} else {
|
||||
exception.report("ReaderActivity::onError")
|
||||
exception.report()
|
||||
}
|
||||
} else {
|
||||
onCancel(dialog)
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package org.koitharu.kotatsu.reader.ui
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
|
||||
import org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.databinding.DialogReaderConfigBinding
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@Deprecated("Not in use")
|
||||
class ReaderConfigDialog :
|
||||
AlertDialogFragment<DialogReaderConfigBinding>(),
|
||||
CheckableButtonGroup.OnCheckedChangeListener {
|
||||
|
||||
private lateinit var mode: ReaderMode
|
||||
|
||||
override fun onInflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
) = DialogReaderConfigBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mode = arguments?.getInt(ARG_MODE)
|
||||
?.let { ReaderMode.valueOf(it) }
|
||||
?: ReaderMode.STANDARD
|
||||
}
|
||||
|
||||
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||
return super.onBuildDialog(builder)
|
||||
.setTitle(R.string.read_mode)
|
||||
.setPositiveButton(R.string.done, null)
|
||||
.setCancelable(true)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD
|
||||
binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED
|
||||
binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON
|
||||
|
||||
binding.checkableGroup.onCheckedChangeListener = this
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
(
|
||||
(parentFragment as? Callback)
|
||||
?: (activity as? Callback)
|
||||
)?.onReaderModeChanged(mode)
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
|
||||
override fun onCheckedChanged(group: CheckableButtonGroup, checkedId: Int) {
|
||||
mode = when (checkedId) {
|
||||
R.id.button_standard -> ReaderMode.STANDARD
|
||||
R.id.button_webtoon -> ReaderMode.WEBTOON
|
||||
R.id.button_reversed -> ReaderMode.REVERSED
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
|
||||
fun onReaderModeChanged(mode: ReaderMode)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val TAG = "ReaderConfigDialog"
|
||||
private const val ARG_MODE = "mode"
|
||||
|
||||
fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigDialog().withArgs(1) {
|
||||
putInt(ARG_MODE, mode.id)
|
||||
}.show(fm, TAG)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
import org.koitharu.kotatsu.reader.ui.pager.reversed.ReversedReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment
|
||||
import org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonReaderFragment
|
||||
import java.util.*
|
||||
import java.util.EnumMap
|
||||
|
||||
class ReaderManager(
|
||||
private val fragmentManager: FragmentManager,
|
||||
@@ -35,11 +35,15 @@ class ReaderManager(
|
||||
fun replace(newMode: ReaderMode) {
|
||||
val readerClass = requireNotNull(modeMap[newMode])
|
||||
fragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(containerResId, readerClass, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun replace(reader: BaseReader<*>) {
|
||||
fragmentManager.commit { replace(containerResId, reader) }
|
||||
fragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(containerResId, reader)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,22 @@ import androidx.lifecycle.viewModelScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import java.util.*
|
||||
import javax.inject.Provider
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.domain.MangaIntent
|
||||
@@ -21,7 +33,11 @@ import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.*
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
@@ -39,6 +55,8 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.requireValue
|
||||
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||
import java.util.Date
|
||||
import javax.inject.Provider
|
||||
|
||||
private const val BOUNDS_PAGE_OFFSET = 2
|
||||
private const val PREFETCH_LIMIT = 10
|
||||
@@ -88,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,
|
||||
@@ -116,6 +140,10 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
|
||||
init {
|
||||
loadImpl()
|
||||
settings.observe()
|
||||
.onEach { key ->
|
||||
if (key == AppSettings.KEY_READER_SLIDER) notifyStateChanged()
|
||||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
@@ -348,6 +376,7 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
chaptersTotal = chapters.size(),
|
||||
totalPages = if (chapter != null) chaptersLoader.getPagesCount(chapter.id) else 0,
|
||||
currentPage = state?.page ?: 0,
|
||||
isSliderEnabled = settings.isReaderSliderEnabled,
|
||||
)
|
||||
uiState.postValue(newState)
|
||||
}
|
||||
|
||||
@@ -103,8 +103,8 @@ class ColorFilterConfigActivity :
|
||||
}
|
||||
|
||||
private fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) {
|
||||
binding.sliderBrightness.value = readerColorFilter?.brightness ?: 0f
|
||||
binding.sliderContrast.value = readerColorFilter?.contrast ?: 0f
|
||||
binding.sliderBrightness.setValueRounded(readerColorFilter?.brightness ?: 0f)
|
||||
binding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f)
|
||||
binding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.reader.ui.ReaderViewModel
|
||||
import org.koitharu.kotatsu.reader.ui.colorfilter.ColorFilterConfigActivity
|
||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||
import org.koitharu.kotatsu.utils.ScreenOrientationHelper
|
||||
import org.koitharu.kotatsu.utils.ext.setValueRounded
|
||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
import org.koitharu.kotatsu.utils.ext.withArgs
|
||||
|
||||
@@ -65,7 +66,7 @@ class ReaderConfigBottomSheet :
|
||||
binding.sliderTimer.setLabelFormatter(PageSwitchTimer.DelayLabelFormatter(view.resources))
|
||||
|
||||
findCallback()?.run {
|
||||
binding.sliderTimer.value = pageSwitchDelay
|
||||
binding.sliderTimer.setValueRounded(pageSwitchDelay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,13 +76,16 @@ class ReaderConfigBottomSheet :
|
||||
startActivity(SettingsActivity.newReaderSettingsIntent(v.context))
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
R.id.button_save_page -> {
|
||||
val page = viewModel.getCurrentPage() ?: return
|
||||
viewModel.saveCurrentPage(page, savePageRequest)
|
||||
}
|
||||
|
||||
R.id.button_screen_rotate -> {
|
||||
orientationHelper?.toggleOrientation()
|
||||
}
|
||||
|
||||
R.id.button_color_filter -> {
|
||||
val page = viewModel.getCurrentPage() ?: return
|
||||
val manga = viewModel.manga ?: return
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,8 +7,13 @@ data class ReaderUiState(
|
||||
val chaptersTotal: Int,
|
||||
val currentPage: Int,
|
||||
val totalPages: Int,
|
||||
private val isSliderEnabled: Boolean,
|
||||
) {
|
||||
|
||||
fun isSliderAvailable(): Boolean {
|
||||
return isSliderEnabled && totalPages > 1 && currentPage < totalPages
|
||||
}
|
||||
|
||||
fun computePercent(): Float {
|
||||
val ppc = 1f / chaptersTotal
|
||||
val chapterIndex = chapterNumber - 1
|
||||
|
||||
@@ -35,6 +35,10 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
adapter = webtoonAdapter
|
||||
addOnPageScrollListener(PageScrollListener())
|
||||
}
|
||||
|
||||
viewModel.isWebtoonZoomEnabled.observe(viewLifecycleOwner) {
|
||||
binding.frame.isZoomEnable = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
|
||||
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
|
||||
import kotlin.text.Typography.dagger
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MangaListActivity :
|
||||
@@ -41,6 +42,7 @@ class MangaListActivity :
|
||||
val fm = supportFragmentManager
|
||||
if (fm.findFragmentById(R.id.container) == null) {
|
||||
fm.commit {
|
||||
setReorderingAllowed(true)
|
||||
val fragment = if (source == MangaSource.LOCAL) {
|
||||
LocalListFragment.newInstance()
|
||||
} else {
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.databinding.ActivitySearchBinding
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
|
||||
import org.koitharu.kotatsu.utils.ext.showKeyboard
|
||||
import kotlin.text.Typography.dagger
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SearchActivity : BaseActivity<ActivitySearchBinding>(), SearchView.OnQueryTextListener {
|
||||
@@ -61,6 +62,7 @@ class SearchActivity : BaseActivity<ActivitySearchBinding>(), SearchView.OnQuery
|
||||
}
|
||||
title = query
|
||||
supportFragmentManager.commit {
|
||||
setReorderingAllowed(true)
|
||||
replace(R.id.container, SearchFragment.newInstance(source, q))
|
||||
}
|
||||
binding.searchView.clearFocus()
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
@@ -91,8 +91,8 @@ class ToolsFragment :
|
||||
binding.cardUpdate.root.isVisible = false
|
||||
return
|
||||
}
|
||||
binding.cardUpdate.textPrimary.text = getString(R.string.app_update_available_s, version.name)
|
||||
binding.cardUpdate.textSecondary.text = version.description
|
||||
binding.cardUpdate.textSecondary.text = getString(R.string.new_version_s, version.name)
|
||||
binding.cardUpdate.textChangelog.text = version.description
|
||||
binding.cardUpdate.root.isVisible = true
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>>(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.slider.LabelFormatter
|
||||
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.BaseBottomSheet
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.SheetShelfSizeBinding
|
||||
import org.koitharu.kotatsu.utils.ext.setValueRounded
|
||||
import org.koitharu.kotatsu.utils.progress.IntPercentLabelFormatter
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ShelfSizeBottomSheet :
|
||||
@@ -51,9 +51,10 @@ class ShelfSizeBottomSheet :
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
val slider = binding.sliderGrid
|
||||
when (v.id) {
|
||||
R.id.button_small -> binding.sliderGrid.value -= binding.sliderGrid.stepSize
|
||||
R.id.button_large -> binding.sliderGrid.value += binding.sliderGrid.stepSize
|
||||
R.id.button_small -> slider.setValueRounded(slider.value - slider.stepSize)
|
||||
R.id.button_large -> slider.setValueRounded(slider.value + slider.stepSize)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ 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
|
||||
import kotlin.text.Typography.dagger
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SuggestionsActivity :
|
||||
@@ -28,6 +29,7 @@ class SuggestionsActivity :
|
||||
val fm = supportFragmentManager
|
||||
if (fm.findFragmentById(R.id.container) == null) {
|
||||
fm.commit {
|
||||
setReorderingAllowed(true)
|
||||
val fragment = SuggestionsFragment.newInstance()
|
||||
replace(R.id.container, fragment)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 :
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
62
app/src/main/java/org/koitharu/kotatsu/utils/ViewBadge.kt
Normal file
62
app/src/main/java/org/koitharu/kotatsu/utils/ViewBadge.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
package org.koitharu.kotatsu.utils
|
||||
|
||||
import android.view.View
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.google.android.material.badge.BadgeUtils
|
||||
|
||||
class ViewBadge(
|
||||
private val anchor: View,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) : View.OnLayoutChangeListener, DefaultLifecycleObserver {
|
||||
|
||||
private var badgeDrawable: BadgeDrawable? = null
|
||||
|
||||
var counter: Int
|
||||
get() = badgeDrawable?.number ?: 0
|
||||
set(value) {
|
||||
val badge = badgeDrawable ?: initBadge()
|
||||
badge.number = value
|
||||
badge.isVisible = value > 0
|
||||
}
|
||||
|
||||
init {
|
||||
lifecycleOwner.lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onLayoutChange(
|
||||
v: View?,
|
||||
left: Int,
|
||||
top: Int,
|
||||
right: Int,
|
||||
bottom: Int,
|
||||
oldLeft: Int,
|
||||
oldTop: Int,
|
||||
oldRight: Int,
|
||||
oldBottom: Int,
|
||||
) {
|
||||
val badge = badgeDrawable ?: return
|
||||
BadgeUtils.setBadgeDrawableBounds(badge, anchor, null)
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
super.onDestroy(owner)
|
||||
clearBadge()
|
||||
}
|
||||
|
||||
private fun initBadge(): BadgeDrawable {
|
||||
val badge = BadgeDrawable.create(anchor.context)
|
||||
anchor.addOnLayoutChangeListener(this)
|
||||
BadgeUtils.attachBadgeDrawable(badge, anchor)
|
||||
badgeDrawable = badge
|
||||
return badge
|
||||
}
|
||||
|
||||
private fun clearBadge() {
|
||||
val badge = badgeDrawable ?: return
|
||||
anchor.removeOnLayoutChangeListener(this)
|
||||
BadgeUtils.detachBadgeDrawable(badge, anchor)
|
||||
badgeDrawable = null
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -5,7 +5,10 @@ import android.content.res.Resources
|
||||
import androidx.collection.arraySetOf
|
||||
import kotlinx.coroutines.CancellationException
|
||||
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
|
||||
@@ -20,6 +23,8 @@ import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
|
||||
private const val MSG_NO_SPACE_LEFT = "No space left on device"
|
||||
|
||||
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
|
||||
is AuthRequiredException -> resources.getString(R.string.auth_required)
|
||||
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required)
|
||||
@@ -41,20 +46,34 @@ 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)
|
||||
|
||||
private fun getDisplayMessage(msg: String?, resources: Resources): String? = when {
|
||||
msg.isNullOrEmpty() -> null
|
||||
msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun Throwable.isReportable(): Boolean {
|
||||
return this is Error || this.javaClass in reportableExceptions
|
||||
}
|
||||
|
||||
fun Throwable.report(message: String?) {
|
||||
val exception = CaughtException(this, message)
|
||||
fun Throwable.report() {
|
||||
val exception = CaughtException(this, "${javaClass.simpleName}($message)")
|
||||
exception.sendWithAcra()
|
||||
}
|
||||
|
||||
private val reportableExceptions = arraySetOf<Class<*>>(
|
||||
ParseException::class.java,
|
||||
JSONException::class.java,
|
||||
RuntimeException::class.java,
|
||||
IllegalStateException::class.java,
|
||||
IllegalArgumentException::class.java,
|
||||
|
||||
@@ -127,7 +127,12 @@ fun <T> RecyclerView.ViewHolder.getItem(clazz: Class<T>): T? {
|
||||
|
||||
fun Slider.setValueRounded(newValue: Float) {
|
||||
val step = stepSize
|
||||
value = (newValue / step).roundToInt() * step
|
||||
val roundedValue = if (step <= 0f) {
|
||||
newValue
|
||||
} else {
|
||||
(newValue / step).roundToInt() * step
|
||||
}
|
||||
value = roundedValue.coerceIn(valueFrom, valueTo)
|
||||
}
|
||||
|
||||
val RecyclerView.isScrolledToTop: Boolean
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
4
app/src/main/res/color/bottom_menu_active_indicator.xml
Normal file
4
app/src/main/res/color/bottom_menu_active_indicator.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
41
app/src/main/res/drawable/ic_empty_common.xml
Normal file
41
app/src/main/res/drawable/ic_empty_common.xml
Normal file
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
@@ -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
@@ -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>
|
||||
@@ -36,6 +36,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="@dimen/list_spacing"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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_margin="16dp"
|
||||
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: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>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="@dimen/margin_normal"
|
||||
android:paddingHorizontal="?attr/dialogPreferredPadding"
|
||||
android:paddingTop="@dimen/margin_normal">
|
||||
|
||||
<TextView
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user