Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
4b6b2c3e12
|
|||
|
51300e30bd
|
|||
|
399ac07fb3
|
|||
|
|
eeba161235 | ||
|
|
088a388812 | ||
|
|
943bba3ee8 | ||
|
18c3229200
|
|||
|
9b6f511ac6
|
|||
|
74ca19a931
|
|||
|
2684a7384e
|
|||
|
2c561824ef
|
|||
|
61e9796269
|
|||
|
54597eb8f0
|
|||
|
|
e07ea0552f | ||
| b8e90719ce | |||
|
|
2ec716973c | ||
|
|
0000c97e6a | ||
|
|
70684de683 | ||
|
|
5a2288eb2d | ||
|
|
6a023fa976 | ||
| 3cc0fbe7bc | |||
|
b3b022807a
|
|||
|
ba0ea5a9fc
|
|||
|
ca1380e2b1
|
|||
|
05dbd11fc1
|
|||
|
aa650d44d3
|
|||
|
99b698ad12
|
|||
|
2f9c2d9ab6
|
|||
|
ab753787b0
|
|||
| 478ca351eb | |||
|
|
559b2cfd64 |
@@ -14,10 +14,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'org.koitharu.kotatsu'
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
versionCode 571
|
||||
versionName '6.0-a2'
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 574
|
||||
versionName = '6.0.1'
|
||||
generatedDensities = []
|
||||
testInstrumentationRunner "org.koitharu.kotatsu.HiltTestRunner"
|
||||
ksp {
|
||||
@@ -81,11 +81,11 @@ afterEvaluate {
|
||||
}
|
||||
dependencies {
|
||||
//noinspection GradleDependency
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:06a2aa6f97') {
|
||||
implementation('com.github.KotatsuApp:kotatsu-parsers:3a76504380') {
|
||||
exclude group: 'org.json', module: 'json'
|
||||
}
|
||||
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.0'
|
||||
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.10'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
|
||||
45
app/src/debug/kotlin/org/koitharu/kotatsu/KotatsuApp.kt
Normal file
45
app/src/debug/kotlin/org/koitharu/kotatsu/KotatsuApp.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.koitharu.kotatsu
|
||||
|
||||
import android.content.Context
|
||||
import android.os.StrictMode
|
||||
import androidx.fragment.app.strictmode.FragmentStrictMode
|
||||
import org.koitharu.kotatsu.core.BaseApp
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
|
||||
class KotatsuApp : BaseApp() {
|
||||
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
enableStrictMode()
|
||||
}
|
||||
|
||||
private fun enableStrictMode() {
|
||||
StrictMode.setThreadPolicy(
|
||||
StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
StrictMode.setVmPolicy(
|
||||
StrictMode.VmPolicy.Builder()
|
||||
.detectAll()
|
||||
.setClassInstanceLimit(LocalMangaRepository::class.java, 1)
|
||||
.setClassInstanceLimit(PagesCache::class.java, 1)
|
||||
.setClassInstanceLimit(MangaLoaderContext::class.java, 1)
|
||||
.setClassInstanceLimit(PageLoader::class.java, 1)
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
|
||||
.penaltyDeath()
|
||||
.detectFragmentReuse()
|
||||
// .detectWrongFragmentContainer() FIXME: migrate to ViewPager2
|
||||
.detectRetainInstanceUsage()
|
||||
.detectSetUserVisibleHint()
|
||||
.detectFragmentTagUsage()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -148,13 +148,21 @@
|
||||
android:name="org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity"
|
||||
android:label="@string/manage_categories" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity"
|
||||
android:name="org.koitharu.kotatsu.widget.shelf.ShelfWidgetConfigActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/manga_shelf">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.widget.recent.RecentWidgetConfigActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/recent_manga">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.search.ui.multi.MultiSearchActivity"
|
||||
android:label="@string/search" />
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
@@ -40,8 +39,4 @@ fun bookmarkListAD(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewThumb.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
@@ -42,8 +41,4 @@ fun bookmarkLargeAD(
|
||||
}
|
||||
binding.progressView.percent = item.percent
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewThumb.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
package org.koitharu.kotatsu.browser.cloudflare
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import coil.EventListener
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
|
||||
class CaptchaNotifier(
|
||||
private val context: Context,
|
||||
) : ImageRequest.Listener {
|
||||
) : EventListener {
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun notify(exception: CloudFlareProtectedException) {
|
||||
val manager = NotificationManagerCompat.from(context)
|
||||
if (!manager.areNotificationsEnabled()) {
|
||||
if (!context.checkNotificationPermission()) {
|
||||
return
|
||||
}
|
||||
val manager = NotificationManagerCompat.from(context)
|
||||
val channel = NotificationChannelCompat.Builder(CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setName(context.getString(R.string.captcha_required))
|
||||
.setShowBadge(true)
|
||||
|
||||
@@ -13,7 +13,6 @@ import coil.decode.SvgDecoder
|
||||
import coil.disk.DiskCache
|
||||
import coil.util.DebugLogger
|
||||
import dagger.Binds
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -26,6 +25,7 @@ import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.cache.ContentCache
|
||||
import org.koitharu.kotatsu.core.cache.MemoryContentCache
|
||||
import org.koitharu.kotatsu.core.cache.StubContentCache
|
||||
@@ -47,7 +47,7 @@ import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.main.domain.CoverRestorer
|
||||
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
|
||||
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.reader.ui.thumbnails.MangaPageFetcher
|
||||
@@ -91,7 +91,7 @@ interface AppModule {
|
||||
mangaRepositoryFactory: MangaRepository.Factory,
|
||||
imageProxyInterceptor: ImageProxyInterceptor,
|
||||
pageFetcherFactory: MangaPageFetcher.Factory,
|
||||
coverRestorerProvider: Lazy<CoverRestorer>,
|
||||
coverRestoreInterceptor: CoverRestoreInterceptor,
|
||||
): ImageLoader {
|
||||
val diskCacheFactory = {
|
||||
val rootDir = context.externalCacheDir ?: context.cacheDir
|
||||
@@ -108,7 +108,7 @@ interface AppModule {
|
||||
.diskCache(diskCacheFactory)
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
||||
.allowRgb565(context.isLowRamDevice())
|
||||
.eventListenerFactory { coverRestorerProvider.get() }
|
||||
.eventListener(CaptchaNotifier(context))
|
||||
.components(
|
||||
ComponentRegistry.Builder()
|
||||
.add(SvgDecoder.Factory())
|
||||
@@ -116,6 +116,7 @@ interface AppModule {
|
||||
.add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory))
|
||||
.add(pageFetcherFactory)
|
||||
.add(imageProxyInterceptor)
|
||||
.add(coverRestoreInterceptor)
|
||||
.build(),
|
||||
).build()
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package org.koitharu.kotatsu
|
||||
package org.koitharu.kotatsu.core
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.StrictMode
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.fragment.app.strictmode.FragmentStrictMode
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.room.InvalidationTracker
|
||||
import androidx.work.Configuration
|
||||
@@ -20,21 +18,19 @@ import org.acra.config.httpSender
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
import org.acra.sender.HttpSender
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.os.AppValidator
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.WorkServiceStopHelper
|
||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.settings.work.WorkScheduleManager
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
@HiltAndroidApp
|
||||
class KotatsuApp : Application(), Configuration.Provider {
|
||||
open class BaseApp : Application(), Configuration.Provider {
|
||||
|
||||
@Inject
|
||||
lateinit var databaseObservers: Set<@JvmSuppressWildcards InvalidationTracker.Observer>
|
||||
@@ -63,9 +59,6 @@ class KotatsuApp : Application(), Configuration.Provider {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
ACRA.errorReporter.putCustomData("isOriginalApp", appValidator.isOriginalApp.toString())
|
||||
if (BuildConfig.DEBUG) {
|
||||
enableStrictMode()
|
||||
}
|
||||
AppCompatDelegate.setDefaultNightMode(settings.theme)
|
||||
AppCompatDelegate.setApplicationLocales(settings.appLocales)
|
||||
setupActivityLifecycleCallbacks()
|
||||
@@ -136,31 +129,4 @@ class KotatsuApp : Application(), Configuration.Provider {
|
||||
registerActivityLifecycleCallbacks(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableStrictMode() {
|
||||
StrictMode.setThreadPolicy(
|
||||
StrictMode.ThreadPolicy.Builder()
|
||||
.detectAll()
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
StrictMode.setVmPolicy(
|
||||
StrictMode.VmPolicy.Builder()
|
||||
.detectAll()
|
||||
.setClassInstanceLimit(LocalMangaRepository::class.java, 1)
|
||||
.setClassInstanceLimit(PagesCache::class.java, 1)
|
||||
.setClassInstanceLimit(MangaLoaderContext::class.java, 1)
|
||||
.setClassInstanceLimit(PageLoader::class.java, 1)
|
||||
.penaltyLog()
|
||||
.build(),
|
||||
)
|
||||
FragmentStrictMode.defaultPolicy = FragmentStrictMode.Policy.Builder()
|
||||
.penaltyDeath()
|
||||
.detectFragmentReuse()
|
||||
// .detectWrongFragmentContainer() FIXME: migrate to ViewPager2
|
||||
.detectRetainInstanceUsage()
|
||||
.detectSetUserVisibleHint()
|
||||
.detectFragmentTagUsage()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,8 @@ import java.util.Date
|
||||
class TooManyRequestExceptions(
|
||||
val url: String,
|
||||
val retryAt: Date?,
|
||||
) : IOException()
|
||||
) : IOException() {
|
||||
|
||||
val retryAfter: Long
|
||||
get() = if (retryAt == null) 0 else (retryAt.time - System.currentTimeMillis()).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import coil.network.HttpException
|
||||
import coil.request.Options
|
||||
import coil.size.Size
|
||||
import coil.size.pxOrElse
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -25,11 +26,13 @@ import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import java.net.HttpURLConnection
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
private const val FALLBACK_SIZE = 9999 // largest icon
|
||||
|
||||
@@ -55,13 +58,16 @@ class FaviconFetcher(
|
||||
options.size.height.pxOrElse { FALLBACK_SIZE },
|
||||
)
|
||||
var favicons = repo.getFavicons()
|
||||
var lastError: Exception? = null
|
||||
while (favicons.isNotEmpty()) {
|
||||
val icon = favicons.find(sizePx) ?: throwNSEE()
|
||||
coroutineContext.ensureActive()
|
||||
val icon = favicons.find(sizePx) ?: throwNSEE(lastError)
|
||||
val response = try {
|
||||
loadIcon(icon.url, mangaSource)
|
||||
} catch (e: CloudFlareProtectedException) {
|
||||
throw e
|
||||
} catch (e: HttpException) {
|
||||
lastError = e
|
||||
favicons -= icon
|
||||
continue
|
||||
}
|
||||
@@ -75,7 +81,7 @@ class FaviconFetcher(
|
||||
dataSource = response.toDataSource(),
|
||||
)
|
||||
}
|
||||
throwNSEE()
|
||||
throwNSEE(lastError)
|
||||
}
|
||||
|
||||
private suspend fun loadIcon(url: String, source: MangaSource): Response {
|
||||
@@ -105,14 +111,14 @@ class FaviconFetcher(
|
||||
)
|
||||
}
|
||||
|
||||
private fun writeToDiskCache(body: ResponseBody): DiskCache.Snapshot? {
|
||||
private suspend fun writeToDiskCache(body: ResponseBody): DiskCache.Snapshot? {
|
||||
if (!options.diskCachePolicy.writeEnabled || body.contentLength() == 0L) {
|
||||
return null
|
||||
}
|
||||
val editor = diskCache.value?.openEditor(diskCacheKey) ?: return null
|
||||
try {
|
||||
fileSystem.write(editor.data) {
|
||||
body.source().readAll(this)
|
||||
writeAllCancellable(body.source())
|
||||
}
|
||||
return editor.commitAndOpenSnapshot()
|
||||
} catch (e: Throwable) {
|
||||
@@ -154,7 +160,13 @@ class FaviconFetcher(
|
||||
append(height.toString())
|
||||
}
|
||||
|
||||
private fun throwNSEE(): Nothing = throw NoSuchElementException("No favicons found")
|
||||
private fun throwNSEE(lastError: Exception?): Nothing {
|
||||
if (lastError != null) {
|
||||
throw lastError
|
||||
} else {
|
||||
throw NoSuchElementException("No favicons found")
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
context: Context,
|
||||
|
||||
@@ -51,6 +51,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
val isAmoledTheme: Boolean
|
||||
get() = prefs.getBoolean(KEY_THEME_AMOLED, false)
|
||||
|
||||
val isFavoritesNavItemFirst: Boolean
|
||||
get() = (prefs.getString(KEY_FIRST_NAV_ITEM, null)?.toIntOrNull() ?: 0) == 1
|
||||
|
||||
var gridSize: Int
|
||||
get() = prefs.getInt(KEY_GRID_SIZE, 100)
|
||||
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
|
||||
@@ -452,6 +455,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_LOCAL_MANGA_DIRS = "local_manga_dirs"
|
||||
const val KEY_DISABLE_NSFW = "no_nsfw"
|
||||
const val KEY_RELATED_MANGA = "related_manga"
|
||||
const val KEY_FIRST_NAV_ITEM = "nav_first"
|
||||
|
||||
// About
|
||||
const val KEY_APP_UPDATE = "app_update"
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
package org.koitharu.kotatsu.core.prefs
|
||||
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.core.content.edit
|
||||
|
||||
private const val CATEGORY_ID = "cat_id"
|
||||
private const val BACKGROUND = "bg"
|
||||
|
||||
class AppWidgetConfig(context: Context, val widgetId: Int) {
|
||||
class AppWidgetConfig(
|
||||
context: Context,
|
||||
cls: Class<out AppWidgetProvider>,
|
||||
val widgetId: Int,
|
||||
) {
|
||||
|
||||
private val prefs = context.getSharedPreferences("appwidget_$widgetId", Context.MODE_PRIVATE)
|
||||
private val prefs = context.getSharedPreferences("appwidget_${cls.simpleName}_$widgetId", Context.MODE_PRIVATE)
|
||||
|
||||
var categoryId: Long
|
||||
get() = prefs.getLong(CATEGORY_ID, 0L)
|
||||
set(value) = prefs.edit { putLong(CATEGORY_ID, value) }
|
||||
|
||||
var hasBackground: Boolean
|
||||
get() = prefs.getBoolean(BACKGROUND, Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||
set(value) = prefs.edit { putBoolean(BACKGROUND, value) }
|
||||
|
||||
fun clear() {
|
||||
prefs.edit { clear() }
|
||||
}
|
||||
|
||||
fun copyFrom(other: AppWidgetConfig) {
|
||||
prefs.edit {
|
||||
clear()
|
||||
putLong(CATEGORY_ID, other.categoryId)
|
||||
putBoolean(BACKGROUND, other.hasBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package org.koitharu.kotatsu.core.prefs
|
||||
enum class ReaderMode(val id: Int) {
|
||||
|
||||
STANDARD(1),
|
||||
WEBTOON(2),
|
||||
REVERSED(3);
|
||||
REVERSED(3),
|
||||
WEBTOON(2);
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package org.koitharu.kotatsu.core.ui
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.CallSuper
|
||||
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||
|
||||
abstract class BaseAppWidgetProvider : AppWidgetProvider() {
|
||||
|
||||
@CallSuper
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
appWidgetIds.forEach { id ->
|
||||
val config = AppWidgetConfig(context, javaClass, id)
|
||||
val views = onUpdateWidget(context, config)
|
||||
appWidgetManager.updateAppWidget(id, views)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||
super.onDeleted(context, appWidgetIds)
|
||||
for (id in appWidgetIds) {
|
||||
AppWidgetConfig(context, javaClass, id).clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestored(context: Context, oldWidgetIds: IntArray, newWidgetIds: IntArray) {
|
||||
super.onRestored(context, oldWidgetIds, newWidgetIds)
|
||||
if (oldWidgetIds.size != newWidgetIds.size) {
|
||||
return
|
||||
}
|
||||
for (i in oldWidgetIds.indices) {
|
||||
val oldId = oldWidgetIds[i]
|
||||
val newId = newWidgetIds[i]
|
||||
val oldConfig = AppWidgetConfig(context, javaClass, oldId)
|
||||
val newConfig = AppWidgetConfig(context, javaClass, newId)
|
||||
newConfig.copyFrom(oldConfig)
|
||||
oldConfig.clear()
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun onUpdateWidget(
|
||||
context: Context,
|
||||
config: AppWidgetConfig,
|
||||
): RemoteViews
|
||||
}
|
||||
@@ -25,4 +25,9 @@ class ActivityRecreationHandle @Inject constructor() : DefaultActivityLifecycleC
|
||||
val snapshot = activities.keys.toList()
|
||||
snapshot.forEach { ActivityCompat.recreate(it) }
|
||||
}
|
||||
|
||||
fun recreate(cls: Class<out Activity>) {
|
||||
val activity = activities.keys.find { x -> x.javaClass == cls } ?: return
|
||||
ActivityCompat.recreate(activity)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class TipView @JvmOverloads constructor(
|
||||
val shapeAppearanceModel = ShapeAppearanceModel.builder(context, attrs, defStyleAttr, 0).build()
|
||||
background = MaterialShapeDrawable(shapeAppearanceModel).also {
|
||||
it.fillColor = getColorStateList(R.styleable.TipView_cardBackgroundColor)
|
||||
?: context.getThemeColorStateList(materialR.attr.colorBackgroundFloating)
|
||||
?: context.getThemeColorStateList(R.attr.m3ColorExploreButton)
|
||||
it.strokeWidth = getDimension(R.styleable.TipView_strokeWidth, 0f)
|
||||
it.strokeColor = getColorStateList(R.styleable.TipView_strokeColor)
|
||||
it.elevation = getDimension(R.styleable.TipView_elevation, 0f)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.koitharu.kotatsu.core.util
|
||||
|
||||
import androidx.collection.ArrayMap
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
||||
class CompositeMutex2<T : Any> : Set<T> {
|
||||
|
||||
private val delegates = ArrayMap<T, Mutex>()
|
||||
|
||||
override val size: Int
|
||||
get() = delegates.size
|
||||
|
||||
override fun contains(element: T): Boolean {
|
||||
return delegates.containsKey(element)
|
||||
}
|
||||
|
||||
override fun containsAll(elements: Collection<T>): Boolean {
|
||||
return elements.all { x -> delegates.containsKey(x) }
|
||||
}
|
||||
|
||||
override fun isEmpty(): Boolean {
|
||||
return delegates.isEmpty
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<T> {
|
||||
return delegates.keys.iterator()
|
||||
}
|
||||
|
||||
suspend fun lock(element: T) {
|
||||
val mutex = synchronized(delegates) {
|
||||
delegates.getOrPut(element) {
|
||||
Mutex()
|
||||
}
|
||||
}
|
||||
mutex.lock()
|
||||
}
|
||||
|
||||
fun unlock(element: T) {
|
||||
synchronized(delegates) {
|
||||
delegates.remove(element)?.unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ fun <T> SharedPreferences.observe(key: String, valueProducer: suspend () -> T):
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
|
||||
fun Lifecycle.postDelayed(runnable: Runnable, delay: Long) {
|
||||
fun Lifecycle.postDelayed(delay: Long, runnable: Runnable) {
|
||||
coroutineScope.launch {
|
||||
delay(delay)
|
||||
runnable.run()
|
||||
|
||||
@@ -12,7 +12,6 @@ import coil.request.SuccessResult
|
||||
import coil.util.CoilUtils
|
||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.ui.image.RegionBitmapDecoder
|
||||
import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
@@ -29,7 +28,6 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
|
||||
.data(data)
|
||||
.lifecycle(lifecycleOwner)
|
||||
.crossfade(context)
|
||||
.addListener(CaptchaNotifier(context.applicationContext))
|
||||
.target(this)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.lifecycle.RetainedLifecycle
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||
@@ -70,3 +71,11 @@ private fun Lifecycle.removeObserverFromAnyThread(observer: LifecycleObserver) {
|
||||
removeObserver(observer)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> Deferred<T>.getCompletionResultOrNull(): Result<T>? = if (isCompleted) {
|
||||
getCompletionExceptionOrNull()?.let { error ->
|
||||
Result.failure(error)
|
||||
} ?: Result.success(getCompleted())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.databinding.ItemScrobblingInfoBinding
|
||||
@@ -37,8 +36,4 @@ fun scrobblingInfoAD(
|
||||
context.resources.getStringArray(R.array.scrobbling_statuses).getOrNull(it.ordinal)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
@@ -135,8 +134,4 @@ fun downloadItemAD(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import okio.IOException
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.TooManyRequestExceptions
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
@@ -277,7 +278,12 @@ class DownloadWorker @AssistedInject constructor(
|
||||
publishState(currentState.copy(isPaused = false, error = null))
|
||||
} else {
|
||||
countDown--
|
||||
delay(DOWNLOAD_ERROR_DELAY)
|
||||
val retryDelay = if (e is TooManyRequestExceptions) {
|
||||
e.retryAfter + DOWNLOAD_ERROR_DELAY
|
||||
} else {
|
||||
DOWNLOAD_ERROR_DELAY
|
||||
}
|
||||
delay(retryDelay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,14 @@ class MangaSourcesRepository @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setPositions(sources: List<MangaSource>) {
|
||||
db.withTransaction {
|
||||
for ((index, item) in sources.withIndex()) {
|
||||
dao.setSortKey(item.name, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setPosition(source: MangaSource, index: Int) {
|
||||
db.withTransaction {
|
||||
val all = dao.findAll().toMutableList()
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
@@ -82,10 +81,6 @@ fun exploreRecommendationItemAD(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
fun exploreSourceListItemAD(
|
||||
@@ -113,10 +108,6 @@ fun exploreSourceListItemAD(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewIcon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
fun exploreSourceGridItemAD(
|
||||
@@ -144,8 +135,4 @@ fun exploreSourceGridItemAD(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewIcon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
@@ -89,10 +88,4 @@ fun categoryAD(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
coverViews.forEach {
|
||||
it.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class FilterSheetFragment :
|
||||
val adapter = FilterAdapter(filter, this)
|
||||
binding.recyclerView.adapter = adapter
|
||||
filter.filterItems.observe(viewLifecycleOwner, adapter)
|
||||
binding.recyclerView.addItemDecoration(TypedListSpacingDecoration(binding.root.context, false))
|
||||
binding.recyclerView.addItemDecoration(TypedListSpacingDecoration(binding.root.context, true))
|
||||
|
||||
if (dialog == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
binding.recyclerView.scrollIndicators = 0
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.list.ui.adapter
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||
@@ -27,8 +26,4 @@ fun emptyHintAD(
|
||||
binding.textSecondary.setTextAndVisible(item.textSecondary)
|
||||
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.icon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.koitharu.kotatsu.list.ui.adapter
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextAndVisible
|
||||
@@ -31,8 +30,4 @@ fun emptyStateListAD(
|
||||
binding.buttonRetry.setTextAndVisible(item.actionStringRes)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.icon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,10 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
|
||||
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaGridModel
|
||||
@@ -54,11 +52,4 @@ fun mangaGridItemAD(
|
||||
}
|
||||
badge = itemView.bindBadge(badge, item.counter)
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
itemView.clearBadge(badge)
|
||||
binding.progressView.percent = PROGRESS_NONE
|
||||
badge = null
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,13 +11,11 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
|
||||
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel
|
||||
@@ -73,11 +71,4 @@ fun mangaListDetailedItemAD(
|
||||
binding.ratingBar.rating = binding.ratingBar.numStars * item.manga.rating
|
||||
badge = itemView.bindBadge(badge, item.counter)
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
itemView.clearBadge(badge)
|
||||
binding.progressView.percent = PROGRESS_NONE
|
||||
badge = null
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
@@ -48,10 +47,4 @@ fun mangaListItemAD(
|
||||
}
|
||||
badge = itemView.bindBadge(badge, item.counter)
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
itemView.clearBadge(badge)
|
||||
badge = null
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,4 +70,6 @@ class TypedListSpacingDecoration(
|
||||
private fun Rect.set(spacing: Int) = set(spacing, spacing, spacing, spacing)
|
||||
|
||||
private fun ListItemType?.isEdgeToEdge() = this == ListItemType.MANGA_NESTED_GROUP
|
||||
|| this == ListItemType.FILTER_SORT
|
||||
|| this == ListItemType.FILTER_TAG
|
||||
}
|
||||
|
||||
@@ -53,11 +53,11 @@ class LocalStorageManager @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun computeStorageSize() = withContext(Dispatchers.IO) {
|
||||
getAvailableStorageDirs().sumOf { it.computeSize() }
|
||||
getConfiguredStorageDirs().sumOf { it.computeSize() }
|
||||
}
|
||||
|
||||
suspend fun computeAvailableSize() = runInterruptible(Dispatchers.IO) {
|
||||
getAvailableStorageDirs().mapToSet { it.freeSpace }.sum()
|
||||
getConfiguredStorageDirs().mapToSet { it.freeSpace }.sum()
|
||||
}
|
||||
|
||||
suspend fun clearCache(cache: CacheDir) = runInterruptible(Dispatchers.IO) {
|
||||
|
||||
@@ -1,55 +1,65 @@
|
||||
package org.koitharu.kotatsu.main.domain
|
||||
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import coil.EventListener
|
||||
import coil.ImageLoader
|
||||
import androidx.collection.ArraySet
|
||||
import coil.intercept.Interceptor
|
||||
import coil.network.HttpException
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.launch
|
||||
import coil.request.ImageResult
|
||||
import org.jsoup.HttpStatusException
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import java.util.Collections
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class CoverRestorer @Inject constructor(
|
||||
class CoverRestoreInterceptor @Inject constructor(
|
||||
private val dataRepository: MangaDataRepository,
|
||||
private val bookmarksRepository: BookmarksRepository,
|
||||
private val repositoryFactory: MangaRepository.Factory,
|
||||
private val coilProvider: Provider<ImageLoader>,
|
||||
) : EventListener {
|
||||
) : Interceptor {
|
||||
|
||||
override fun onError(request: ImageRequest, result: ErrorResult) {
|
||||
super.onError(request, result)
|
||||
if (!result.throwable.shouldRestore()) {
|
||||
return
|
||||
}
|
||||
request.tags.tag<Manga>()?.let {
|
||||
restoreManga(it, request)
|
||||
}
|
||||
request.tags.tag<Bookmark>()?.let {
|
||||
restoreBookmark(it, request)
|
||||
}
|
||||
}
|
||||
private val blacklist = Collections.synchronizedSet(ArraySet<String>())
|
||||
|
||||
private fun restoreManga(manga: Manga, request: ImageRequest) {
|
||||
request.lifecycle.coroutineScope.launch {
|
||||
val restored = runCatchingCancellable {
|
||||
restoreMangaImpl(manga)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
request.newBuilder().enqueueWith(coilProvider.get())
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val request = chain.request
|
||||
val result = chain.proceed(request)
|
||||
if (result is ErrorResult && result.throwable.shouldRestore()) {
|
||||
request.tags.tag<Manga>()?.let {
|
||||
if (restoreManga(it)) {
|
||||
return chain.proceed(request.newBuilder().build())
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
request.tags.tag<Bookmark>()?.let {
|
||||
if (restoreBookmark(it)) {
|
||||
return chain.proceed(request.newBuilder().build())
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private suspend fun restoreManga(manga: Manga): Boolean {
|
||||
val key = manga.publicUrl
|
||||
if (!blacklist.add(key)) {
|
||||
return false
|
||||
}
|
||||
val restored = runCatchingCancellable {
|
||||
restoreMangaImpl(manga)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
blacklist.remove(key)
|
||||
}
|
||||
return restored
|
||||
}
|
||||
|
||||
private suspend fun restoreMangaImpl(manga: Manga): Boolean {
|
||||
@@ -66,15 +76,18 @@ class CoverRestorer @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreBookmark(bookmark: Bookmark, request: ImageRequest) {
|
||||
request.lifecycle.coroutineScope.launch {
|
||||
val restored = runCatchingCancellable {
|
||||
restoreBookmarkImpl(bookmark)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
request.newBuilder().enqueueWith(coilProvider.get())
|
||||
}
|
||||
private suspend fun restoreBookmark(bookmark: Bookmark): Boolean {
|
||||
val key = bookmark.imageUrl
|
||||
if (!blacklist.add(key)) {
|
||||
return false
|
||||
}
|
||||
val restored = runCatchingCancellable {
|
||||
restoreBookmarkImpl(bookmark)
|
||||
}.getOrDefault(false)
|
||||
if (restored) {
|
||||
blacklist.remove(key)
|
||||
}
|
||||
return restored
|
||||
}
|
||||
|
||||
private suspend fun restoreBookmarkImpl(bookmark: Bookmark): Boolean {
|
||||
@@ -116,9 +116,10 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
|
||||
navigationDelegate = MainNavigationDelegate(
|
||||
navBar = checkNotNull(bottomNav ?: viewBinding.navRail),
|
||||
fragmentManager = supportFragmentManager,
|
||||
settings = settings,
|
||||
)
|
||||
navigationDelegate.addOnFragmentChangedListener(this)
|
||||
navigationDelegate.onCreate()
|
||||
navigationDelegate.onCreate(savedInstanceState)
|
||||
|
||||
appUpdateBadge = OptionsMenuBadgeHelper(viewBinding.toolbar, R.id.action_app_update)
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package org.koitharu.kotatsu.main.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.core.view.isEmpty
|
||||
import androidx.core.view.iterator
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.navigation.NavigationBarView
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
||||
import org.koitharu.kotatsu.core.util.ext.firstVisibleItemPosition
|
||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||
@@ -23,7 +26,10 @@ private const val TAG_PRIMARY = "primary"
|
||||
class MainNavigationDelegate(
|
||||
private val navBar: NavigationBarView,
|
||||
private val fragmentManager: FragmentManager,
|
||||
) : OnBackPressedCallback(false), NavigationBarView.OnItemSelectedListener, NavigationBarView.OnItemReselectedListener {
|
||||
private val settings: AppSettings,
|
||||
) : OnBackPressedCallback(false),
|
||||
NavigationBarView.OnItemSelectedListener,
|
||||
NavigationBarView.OnItemReselectedListener {
|
||||
|
||||
private val listeners = LinkedList<OnFragmentChangedListener>()
|
||||
|
||||
@@ -56,14 +62,26 @@ class MainNavigationDelegate(
|
||||
navBar.selectedItemId = R.id.nav_history
|
||||
}
|
||||
|
||||
fun onCreate() {
|
||||
primaryFragment?.let {
|
||||
onFragmentChanged(it, fromUser = false)
|
||||
val itemId = getItemId(it)
|
||||
fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (navBar.menu.isEmpty()) {
|
||||
val menuRes = if (settings.isFavoritesNavItemFirst) R.menu.nav_bottom_alt else R.menu.nav_bottom
|
||||
navBar.inflateMenu(menuRes)
|
||||
}
|
||||
val fragment = primaryFragment
|
||||
if (fragment != null) {
|
||||
onFragmentChanged(fragment, fromUser = false)
|
||||
val itemId = getItemId(fragment)
|
||||
if (navBar.selectedItemId != itemId) {
|
||||
navBar.selectedItemId = itemId
|
||||
}
|
||||
} ?: onNavigationItemSelected(navBar.selectedItemId)
|
||||
} else {
|
||||
val itemId = if (savedInstanceState == null) {
|
||||
firstItem()?.itemId ?: navBar.selectedItemId
|
||||
} else {
|
||||
navBar.selectedItemId
|
||||
}
|
||||
onNavigationItemSelected(itemId)
|
||||
}
|
||||
}
|
||||
|
||||
fun setCounterAt(position: Int, counter: Int) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||
import org.koitharu.kotatsu.core.util.ext.getCompletionResultOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.isNotEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
@@ -222,12 +223,9 @@ class PageLoader @Inject constructor(
|
||||
}
|
||||
|
||||
private fun Deferred<File>.isValid(): Boolean {
|
||||
return if (isCompleted) {
|
||||
val file = getCompleted()
|
||||
return getCompletionResultOrNull()?.map { file ->
|
||||
file.exists() && file.isNotEmpty()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}?.getOrDefault(false) ?: true
|
||||
}
|
||||
|
||||
private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
|
||||
|
||||
@@ -163,7 +163,7 @@ class ReaderActivity :
|
||||
readerManager.replace(mode)
|
||||
}
|
||||
if (viewBinding.appbarTop.isVisible) {
|
||||
lifecycle.postDelayed(hideUiRunnable, TimeUnit.SECONDS.toMillis(1))
|
||||
lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable)
|
||||
}
|
||||
viewBinding.slider.isRtl = mode == ReaderMode.REVERSED
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.yield
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
@@ -137,12 +138,13 @@ class PageHolderDelegate(
|
||||
state = State.LOADING
|
||||
error = null
|
||||
callback.onLoadingStarted()
|
||||
yield()
|
||||
try {
|
||||
val task = loader.loadPageAsync(data, force)
|
||||
file = coroutineScope {
|
||||
val progressObserver = observeProgress(this, task.progressAsFlow())
|
||||
val file = task.await()
|
||||
progressObserver.cancel()
|
||||
progressObserver.cancelAndJoin()
|
||||
file
|
||||
}
|
||||
state = State.LOADED
|
||||
|
||||
@@ -116,7 +116,7 @@ open class PageHolder(
|
||||
bindingInfo.progressBar.hide()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
final override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_retry -> delegate.retry(boundData?.toMangaPage() ?: return)
|
||||
R.id.button_error_details -> delegate.showErrorDetails(boundData?.url)
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextColorAttr
|
||||
@@ -56,8 +55,4 @@ fun pageThumbnailAD(
|
||||
text = (item.number).toString()
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewThumb.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
|
||||
@@ -49,8 +50,6 @@ class AniListAuthenticator @Inject constructor(
|
||||
runBlocking { repository.authorize(null) }
|
||||
return storage.accessToken
|
||||
}.onFailure {
|
||||
if (BuildConfig.DEBUG) {
|
||||
it.printStackTrace()
|
||||
}
|
||||
it.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.databinding.ItemScrobblingMangaBinding
|
||||
@@ -34,8 +33,4 @@ fun scrobblingMangaAD(
|
||||
binding.textViewTitle.text = item.title
|
||||
binding.ratingBar.rating = item.rating * binding.ratingBar.numStars
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
@@ -35,8 +34,4 @@ fun scrobblingMangaAD(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
|
||||
@@ -49,9 +50,7 @@ class MALAuthenticator @Inject constructor(
|
||||
runBlocking { repository.authorize(null) }
|
||||
return storage.accessToken
|
||||
}.onFailure {
|
||||
if (BuildConfig.DEBUG) {
|
||||
it.printStackTrace()
|
||||
}
|
||||
it.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
|
||||
@@ -49,8 +50,6 @@ class ShikimoriAuthenticator @Inject constructor(
|
||||
runBlocking { repository.authorize(null) }
|
||||
return storage.accessToken
|
||||
}.onFailure {
|
||||
if (BuildConfig.DEBUG) {
|
||||
it.printStackTrace()
|
||||
}
|
||||
it.printStackTraceDebug()
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
||||
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
@@ -51,8 +50,4 @@ fun searchSuggestionSourceAD(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration
|
||||
import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
@@ -66,10 +65,6 @@ private fun searchSuggestionMangaGridAD(
|
||||
}
|
||||
binding.textViewTitle.text = item.title
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionMangaDiffCallback : DiffUtil.ItemCallback<Manga>() {
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.provider.Settings
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.app.LocaleManagerCompat
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -21,8 +20,10 @@ import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
|
||||
import org.koitharu.kotatsu.core.util.ext.getLocalesConfig
|
||||
import org.koitharu.kotatsu.core.util.ext.map
|
||||
import org.koitharu.kotatsu.core.util.ext.postDelayed
|
||||
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.toList
|
||||
import org.koitharu.kotatsu.main.ui.MainActivity
|
||||
import org.koitharu.kotatsu.parsers.util.names
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.settings.utils.ActivityListPreference
|
||||
@@ -92,11 +93,15 @@ class AppearanceSettingsFragment :
|
||||
AppSettings.KEY_APP_LOCALE -> {
|
||||
AppCompatDelegate.setApplicationLocales(settings.appLocales)
|
||||
}
|
||||
|
||||
AppSettings.KEY_FIRST_NAV_ITEM -> {
|
||||
activityRecreationHandle.recreate(MainActivity::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun postRestart() {
|
||||
view?.postDelayed(400) {
|
||||
viewLifecycleOwner.lifecycle.postDelayed(400) {
|
||||
activityRecreationHandle.recreateAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,10 +172,20 @@ class SourcesManageFragment :
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder,
|
||||
): Boolean = viewHolder.itemViewType == target.itemViewType && viewModel.reorderSources(
|
||||
viewHolder.bindingAdapterPosition,
|
||||
target.bindingAdapterPosition,
|
||||
)
|
||||
): Boolean = viewHolder.itemViewType == target.itemViewType
|
||||
|
||||
override fun onMoved(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
fromPos: Int,
|
||||
target: RecyclerView.ViewHolder,
|
||||
toPos: Int,
|
||||
x: Int,
|
||||
y: Int
|
||||
) {
|
||||
super.onMoved(recyclerView, viewHolder, fromPos, target, toPos, x, y)
|
||||
viewModel.reorderSources(fromPos, toPos)
|
||||
}
|
||||
|
||||
override fun canDropOver(
|
||||
recyclerView: RecyclerView,
|
||||
|
||||
@@ -4,17 +4,15 @@ import androidx.annotation.CheckResult
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.plus
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
@@ -29,13 +27,12 @@ import org.koitharu.kotatsu.core.util.ext.toEnumSet
|
||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||
import org.koitharu.kotatsu.parsers.model.ContentType
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.move
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||
import java.util.Locale
|
||||
import java.util.TreeMap
|
||||
import javax.inject.Inject
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
private const val KEY_ENABLED = "!"
|
||||
private const val TIP_REORDER = "src_reorder"
|
||||
@@ -48,7 +45,7 @@ class SourcesManageViewModel @Inject constructor(
|
||||
|
||||
private val expandedGroups = MutableStateFlow(emptySet<String?>())
|
||||
private var searchQuery = MutableStateFlow<String?>(null)
|
||||
private val mutex = Mutex()
|
||||
private var reorderJob: Job? = null
|
||||
|
||||
val content = combine(
|
||||
repository.observeEnabledSources(),
|
||||
@@ -62,23 +59,28 @@ class SourcesManageViewModel @Inject constructor(
|
||||
|
||||
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||
|
||||
fun reorderSources(oldPos: Int, newPos: Int): Boolean {
|
||||
val snapshot = content.value
|
||||
val item = (snapshot[oldPos] as? SourceConfigItem.SourceItem) ?: return false
|
||||
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) return false
|
||||
launchAtomicJob(Dispatchers.Default) {
|
||||
var targetPosition = 0
|
||||
for ((i, x) in snapshot.withIndex()) {
|
||||
if (i == newPos) {
|
||||
break
|
||||
}
|
||||
if (x is SourceConfigItem.SourceItem) {
|
||||
targetPosition++
|
||||
fun reorderSources(oldPos: Int, newPos: Int) {
|
||||
val snapshot = content.value.toMutableList()
|
||||
val prevJob = reorderJob
|
||||
reorderJob = launchJob(Dispatchers.Default) {
|
||||
prevJob?.cancelAndJoin()
|
||||
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) {
|
||||
return@launchJob
|
||||
}
|
||||
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) {
|
||||
return@launchJob
|
||||
}
|
||||
delay(100)
|
||||
snapshot.move(oldPos, newPos)
|
||||
val newSourcesList = snapshot.mapNotNull { x ->
|
||||
if (x is SourceConfigItem.SourceItem && x.isDraggable) {
|
||||
x.source
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
repository.setPosition(item.source, targetPosition)
|
||||
repository.setPositions(newSourcesList)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun canReorder(oldPos: Int, newPos: Int): Boolean {
|
||||
@@ -88,7 +90,7 @@ class SourcesManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
||||
launchAtomicJob(Dispatchers.Default) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
val rollback = repository.setSourceEnabled(source, isEnabled)
|
||||
if (!isEnabled) {
|
||||
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
||||
@@ -97,7 +99,7 @@ class SourcesManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun disableAll() {
|
||||
launchAtomicJob(Dispatchers.Default) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
repository.disableAllSources()
|
||||
}
|
||||
}
|
||||
@@ -116,7 +118,7 @@ class SourcesManageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun onTipClosed(item: SourceConfigItem.Tip) {
|
||||
launchAtomicJob(Dispatchers.Default) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
settings.closeTip(item.key)
|
||||
}
|
||||
}
|
||||
@@ -203,15 +205,6 @@ class SourcesManageViewModel @Inject constructor(
|
||||
return locale.getDisplayLanguage(locale).toTitleCase(locale)
|
||||
}
|
||||
|
||||
private fun launchAtomicJob(
|
||||
context: CoroutineContext = EmptyCoroutineContext,
|
||||
block: suspend CoroutineScope.() -> Unit
|
||||
) = launchJob(start = CoroutineStart.ATOMIC) {
|
||||
mutex.withLock {
|
||||
withContext(context, block)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeTip() = settings.observeAsFlow(AppSettings.KEY_TIPS_CLOSED) {
|
||||
isTipEnabled(TIP_REORDER)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
||||
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||
import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener
|
||||
import org.koitharu.kotatsu.core.util.ext.crossfade
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
@@ -94,10 +93,6 @@ fun sourceConfigItemCheckableDelegate(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewIcon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
fun sourceConfigItemDelegate2(
|
||||
@@ -143,10 +138,6 @@ fun sourceConfigItemDelegate2(
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewIcon.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
fun sourceConfigTipDelegate(
|
||||
|
||||
@@ -10,7 +10,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.HorizontalScrollView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.customview.view.AbsSavedState
|
||||
@@ -19,7 +18,9 @@ import androidx.preference.PreferenceViewHolder
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.ColorScheme
|
||||
import org.koitharu.kotatsu.databinding.ItemColorSchemeBinding
|
||||
import org.koitharu.kotatsu.databinding.PreferenceThemeBinding
|
||||
import java.lang.ref.WeakReference
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class ThemeChooserPreference @JvmOverloads constructor(
|
||||
context: Context,
|
||||
@@ -43,38 +44,44 @@ class ThemeChooserPreference @JvmOverloads constructor(
|
||||
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
val layout = holder.findViewById(R.id.linear) as? LinearLayout ?: return
|
||||
val scrollView = holder.findViewById(R.id.scrollView) as? HorizontalScrollView ?: return
|
||||
val binding = PreferenceThemeBinding.bind(holder.itemView)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
scrollView.suppressLayout(true)
|
||||
layout.suppressLayout(true)
|
||||
binding.scrollView.suppressLayout(true)
|
||||
binding.linear.suppressLayout(true)
|
||||
}
|
||||
layout.removeAllViews()
|
||||
binding.linear.removeAllViews()
|
||||
for (theme in entries) {
|
||||
val context = ContextThemeWrapper(context, theme.styleResId)
|
||||
val item = ItemColorSchemeBinding.inflate(LayoutInflater.from(context), layout, false)
|
||||
item.card.isChecked = theme == currentValue
|
||||
val item = ItemColorSchemeBinding.inflate(LayoutInflater.from(context), binding.linear, false)
|
||||
val isSelected = theme == currentValue
|
||||
item.card.isChecked = isSelected
|
||||
item.card.strokeWidth = if (isSelected) context.resources.getDimensionPixelSize(
|
||||
materialR.dimen.m3_comp_outlined_card_outline_width,
|
||||
) else 0
|
||||
item.textViewTitle.setText(theme.titleResId)
|
||||
item.root.tag = theme
|
||||
item.card.tag = theme
|
||||
item.imageViewCheck.isVisible = theme == currentValue
|
||||
item.root.setOnClickListener(itemClickListener)
|
||||
item.card.setOnClickListener(itemClickListener)
|
||||
layout.addView(item.root)
|
||||
binding.linear.addView(item.root)
|
||||
if (isSelected) {
|
||||
item.root.requestFocus()
|
||||
}
|
||||
}
|
||||
if (lastScrollPosition[0] >= 0) {
|
||||
val scroller = Scroller(scrollView, lastScrollPosition[0])
|
||||
val scroller = Scroller(binding.scrollView, lastScrollPosition[0])
|
||||
scroller.run()
|
||||
scrollView.post(scroller)
|
||||
binding.scrollView.post(scroller)
|
||||
}
|
||||
scrollView.viewTreeObserver.run {
|
||||
binding.scrollView.viewTreeObserver.run {
|
||||
scrollPersistListener?.let { removeOnScrollChangedListener(it) }
|
||||
scrollPersistListener = ScrollPersistListener(WeakReference(scrollView), lastScrollPosition)
|
||||
scrollPersistListener = ScrollPersistListener(WeakReference(binding.scrollView), lastScrollPosition)
|
||||
addOnScrollChangedListener(scrollPersistListener)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
layout.suppressLayout(false)
|
||||
scrollView.suppressLayout(false)
|
||||
binding.linear.suppressLayout(false)
|
||||
binding.scrollView.suppressLayout(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import coil.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.isBold
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
@@ -44,8 +43,4 @@ fun feedItemAD(
|
||||
item.count,
|
||||
)
|
||||
}
|
||||
|
||||
onViewRecycled {
|
||||
binding.imageViewCover.disposeImageRequest()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ import org.koitharu.kotatsu.core.logs.TrackerLogger
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
|
||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||
import org.koitharu.kotatsu.core.util.ext.powerManager
|
||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.trySetForeground
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
@@ -141,7 +140,7 @@ class TrackWorker @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private suspend fun checkUpdatesAsync(tracks: List<TrackingItem>): List<MangaUpdates> {
|
||||
val semaphore = Semaphore(if (isDeviceInUse()) 1 else MAX_PARALLELISM)
|
||||
val semaphore = Semaphore(MAX_PARALLELISM)
|
||||
return channelFlow {
|
||||
for ((track, channelId) in tracks) {
|
||||
launch {
|
||||
@@ -290,15 +289,6 @@ class TrackWorker @AssistedInject constructor(
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
private fun isDeviceInUse(): Boolean {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return false
|
||||
}
|
||||
val powerManager = applicationContext.powerManager ?: return false
|
||||
return !powerManager.isDeviceIdleMode
|
||||
}
|
||||
|
||||
@Reusable
|
||||
class Scheduler @Inject constructor(
|
||||
private val workManager: WorkManager,
|
||||
|
||||
@@ -55,6 +55,7 @@ class RecentListFactory(
|
||||
.data(item.coverUrl)
|
||||
.size(coverSize)
|
||||
.tag(item.source)
|
||||
.tag(item)
|
||||
.transformations(transformation)
|
||||
.build(),
|
||||
).getDrawableOrThrow().toBitmap()
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.koitharu.kotatsu.widget.recent
|
||||
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.databinding.ActivityAppwidgetRecentBinding
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RecentWidgetConfigActivity :
|
||||
BaseActivity<ActivityAppwidgetRecentBinding>(),
|
||||
View.OnClickListener {
|
||||
|
||||
private lateinit var config: AppWidgetConfig
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityAppwidgetRecentBinding.inflate(layoutInflater))
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
||||
}
|
||||
viewBinding.buttonDone.setOnClickListener(this)
|
||||
val appWidgetId = intent?.getIntExtra(
|
||||
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID,
|
||||
) ?: AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
|
||||
finishAfterTransition()
|
||||
return
|
||||
}
|
||||
config = AppWidgetConfig(this, RecentWidgetProvider::class.java, appWidgetId)
|
||||
viewBinding.switchBackground.isChecked = config.hasBackground
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_done -> {
|
||||
config.hasBackground = viewBinding.switchBackground.isChecked
|
||||
updateWidget()
|
||||
setResult(
|
||||
Activity.RESULT_OK,
|
||||
Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId),
|
||||
)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
viewBinding.root.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
bottom = insets.bottom,
|
||||
top = insets.top,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateWidget() {
|
||||
val intent = Intent(this, RecentWidgetProvider::class.java)
|
||||
intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
val ids = intArrayOf(config.widgetId)
|
||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
}
|
||||
@@ -2,43 +2,52 @@ package org.koitharu.kotatsu.widget.recent
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||
import org.koitharu.kotatsu.core.ui.BaseAppWidgetProvider
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
|
||||
class RecentWidgetProvider : AppWidgetProvider() {
|
||||
class RecentWidgetProvider : BaseAppWidgetProvider() {
|
||||
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
appWidgetIds.forEach { id ->
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_recent)
|
||||
val adapter = Intent(context, RecentWidgetService::class.java)
|
||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||
views.setRemoteAdapter(R.id.stackView, adapter)
|
||||
val intent = Intent(context, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_MANGA_READ
|
||||
views.setPendingIntentTemplate(
|
||||
R.id.stackView,
|
||||
PendingIntentCompat.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
true,
|
||||
),
|
||||
)
|
||||
views.setEmptyView(R.id.stackView, R.id.textView_holder)
|
||||
appWidgetManager.updateAppWidget(id, views)
|
||||
}
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stackView)
|
||||
}
|
||||
|
||||
override fun onUpdateWidget(context: Context, config: AppWidgetConfig): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_recent)
|
||||
if (!config.hasBackground) {
|
||||
views.setInt(R.id.widget_root, "setBackgroundColor", Color.TRANSPARENT)
|
||||
} else {
|
||||
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||
}
|
||||
val adapter = Intent(context, RecentWidgetService::class.java)
|
||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||
views.setRemoteAdapter(R.id.stackView, adapter)
|
||||
val intent = Intent(context, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_MANGA_READ
|
||||
views.setPendingIntentTemplate(
|
||||
R.id.stackView,
|
||||
PendingIntentCompat.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
true,
|
||||
),
|
||||
)
|
||||
views.setEmptyView(R.id.stackView, R.id.textView_holder)
|
||||
return views
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -27,7 +28,7 @@ class ShelfListFactory(
|
||||
) : RemoteViewsService.RemoteViewsFactory {
|
||||
|
||||
private val dataSet = ArrayList<Manga>()
|
||||
private val config = AppWidgetConfig(context, widgetId)
|
||||
private val config = AppWidgetConfig(context, ShelfWidgetProvider::class.java, widgetId)
|
||||
private val transformation = RoundedCornersTransformation(
|
||||
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner),
|
||||
)
|
||||
@@ -66,7 +67,8 @@ class ShelfListFactory(
|
||||
.data(item.coverUrl)
|
||||
.size(coverSize)
|
||||
.tag(item.source)
|
||||
.transformations(transformation)
|
||||
.tag(item)
|
||||
.transformations(transformation, TrimTransformation())
|
||||
.build(),
|
||||
).getDrawableOrThrow().toBitmap()
|
||||
}.onSuccess { cover ->
|
||||
|
||||
@@ -8,7 +8,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -19,14 +18,14 @@ import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.databinding.ActivityCategoriesBinding
|
||||
import org.koitharu.kotatsu.databinding.ActivityAppwidgetShelfBinding
|
||||
import org.koitharu.kotatsu.widget.shelf.adapter.CategorySelectAdapter
|
||||
import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ShelfConfigActivity :
|
||||
BaseActivity<ActivityCategoriesBinding>(),
|
||||
class ShelfWidgetConfigActivity :
|
||||
BaseActivity<ActivityAppwidgetShelfBinding>(),
|
||||
OnListItemClickListener<CategoryItem>,
|
||||
View.OnClickListener {
|
||||
|
||||
@@ -37,16 +36,14 @@ class ShelfConfigActivity :
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(ActivityCategoriesBinding.inflate(layoutInflater))
|
||||
setContentView(ActivityAppwidgetShelfBinding.inflate(layoutInflater))
|
||||
supportActionBar?.run {
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
||||
}
|
||||
adapter = CategorySelectAdapter(this)
|
||||
viewBinding.recyclerView.adapter = adapter
|
||||
viewBinding.buttonDone.isVisible = true
|
||||
viewBinding.buttonDone.setOnClickListener(this)
|
||||
viewBinding.fabAdd.hide()
|
||||
val appWidgetId = intent?.getIntExtra(
|
||||
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID,
|
||||
@@ -55,8 +52,9 @@ class ShelfConfigActivity :
|
||||
finishAfterTransition()
|
||||
return
|
||||
}
|
||||
config = AppWidgetConfig(this, appWidgetId)
|
||||
config = AppWidgetConfig(this, ShelfWidgetProvider::class.java, appWidgetId)
|
||||
viewModel.checkedId = config.categoryId
|
||||
viewBinding.switchBackground.isChecked = config.hasBackground
|
||||
|
||||
viewModel.content.observe(this, this::onContentChanged)
|
||||
viewModel.onError.observeEvent(this, SnackbarErrorObserver(viewBinding.recyclerView, null))
|
||||
@@ -66,6 +64,7 @@ class ShelfConfigActivity :
|
||||
when (v.id) {
|
||||
R.id.button_done -> {
|
||||
config.categoryId = viewModel.checkedId
|
||||
config.hasBackground = viewBinding.switchBackground.isChecked
|
||||
updateWidget()
|
||||
setResult(
|
||||
Activity.RESULT_OK,
|
||||
@@ -81,11 +80,6 @@ class ShelfConfigActivity :
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
viewBinding.fabAdd.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||
rightMargin = topMargin + insets.right
|
||||
leftMargin = topMargin + insets.left
|
||||
bottomMargin = topMargin + insets.bottom
|
||||
}
|
||||
viewBinding.recyclerView.updatePadding(
|
||||
left = insets.left,
|
||||
right = insets.right,
|
||||
@@ -2,43 +2,52 @@ package org.koitharu.kotatsu.widget.shelf
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.widget.RemoteViews
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
|
||||
import org.koitharu.kotatsu.core.ui.BaseAppWidgetProvider
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderActivity
|
||||
|
||||
class ShelfWidgetProvider : AppWidgetProvider() {
|
||||
class ShelfWidgetProvider : BaseAppWidgetProvider() {
|
||||
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
appWidgetIds.forEach { id ->
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_shelf)
|
||||
val adapter = Intent(context, ShelfWidgetService::class.java)
|
||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||
views.setRemoteAdapter(R.id.gridView, adapter)
|
||||
val intent = Intent(context, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_MANGA_READ
|
||||
views.setPendingIntentTemplate(
|
||||
R.id.gridView,
|
||||
PendingIntentCompat.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
true,
|
||||
),
|
||||
)
|
||||
views.setEmptyView(R.id.gridView, R.id.textView_holder)
|
||||
appWidgetManager.updateAppWidget(id, views)
|
||||
}
|
||||
super.onUpdate(context, appWidgetManager, appWidgetIds)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.gridView)
|
||||
}
|
||||
|
||||
override fun onUpdateWidget(context: Context, config: AppWidgetConfig): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_shelf)
|
||||
if (!config.hasBackground) {
|
||||
views.setInt(R.id.widget_root, "setBackgroundColor", Color.TRANSPARENT)
|
||||
} else {
|
||||
views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root)
|
||||
}
|
||||
val adapter = Intent(context, ShelfWidgetService::class.java)
|
||||
adapter.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
|
||||
adapter.data = Uri.parse(adapter.toUri(Intent.URI_INTENT_SCHEME))
|
||||
views.setRemoteAdapter(R.id.gridView, adapter)
|
||||
val intent = Intent(context, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_MANGA_READ
|
||||
views.setPendingIntentTemplate(
|
||||
R.id.gridView,
|
||||
PendingIntentCompat.getActivity(
|
||||
context,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT,
|
||||
true,
|
||||
),
|
||||
)
|
||||
views.setEmptyView(R.id.gridView, R.id.textView_holder)
|
||||
return views
|
||||
}
|
||||
}
|
||||
|
||||
7
app/src/main/res/drawable-v31/bg_appwidget_card.xml
Normal file
7
app/src/main/res/drawable-v31/bg_appwidget_card.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/appwidget_corner_radius_inner" />
|
||||
<solid android:color="@android:color/system_accent2_100" />
|
||||
</shape>
|
||||
7
app/src/main/res/drawable-v31/bg_appwidget_root.xml
Normal file
7
app/src/main/res/drawable-v31/bg_appwidget_root.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/appwidget_corner_radius_background" />
|
||||
<solid android:color="@android:color/system_accent2_50" />
|
||||
</shape>
|
||||
@@ -3,5 +3,5 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/appwidget_corner_radius_inner" />
|
||||
<solid android:color="?android:panelColorBackground" />
|
||||
</shape>
|
||||
<solid android:color="@color/kotatsu_primaryContainer" />
|
||||
</shape>
|
||||
|
||||
7
app/src/main/res/drawable/bg_appwidget_root.xml
Normal file
7
app/src/main/res/drawable/bg_appwidget_root.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="@dimen/appwidget_corner_radius_background" />
|
||||
<solid android:color="@color/kotatsu_background" />
|
||||
</shape>
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<item
|
||||
android:id="@+id/checked"
|
||||
android:drawable="@drawable/ic_heart_outline"
|
||||
android:drawable="@drawable/ic_heart"
|
||||
android:state_checked="true" />
|
||||
|
||||
<transition
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
app:headerLayout="@layout/navigation_rail_fab"
|
||||
app:labelVisibilityMode="labeled"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:menu="@menu/nav_bottom" />
|
||||
tools:menu="@menu/nav_bottom" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
39
app/src/main/res/layout/activity_appwidget_recent.xml
Normal file
39
app/src/main/res/layout/activity_appwidget_recent.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?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="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin"
|
||||
tools:title="@string/recent_manga">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_done"
|
||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
|
||||
android:text="@string/done" />
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:text="@string/background"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
app:layout_scrollFlags="scroll" />
|
||||
|
||||
</LinearLayout>
|
||||
70
app/src/main/res/layout/activity_appwidget_shelf.xml
Normal file
70
app/src/main/res/layout/activity_appwidget_shelf.xml
Normal file
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin"
|
||||
tools:title="@string/manga_shelf">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_done"
|
||||
style="@style/Widget.Material3.Button.UnelevatedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
|
||||
android:text="@string/done" />
|
||||
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:checked="true"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:text="@string/background"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
app:layout_scrollFlags="scroll" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical|start"
|
||||
android:paddingStart="?listPreferredItemPaddingStart"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingEnd="?listPreferredItemPaddingEnd"
|
||||
android:singleLine="true"
|
||||
android:text="@string/favourites_categories"
|
||||
android:textAppearance="@style/TextAppearance.Kotatsu.SectionHeader" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingVertical="@dimen/list_spacing_normal"
|
||||
android:scrollbars="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
tools:listitem="@layout/item_checkable_single" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -41,6 +41,7 @@
|
||||
style="@style/Widget.Material3.BottomSheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:backgroundTint="?attr/m3ColorBottomMenuBackground"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone"
|
||||
app:behavior_hideable="false"
|
||||
@@ -51,8 +52,7 @@
|
||||
<FrameLayout
|
||||
android:id="@+id/layout_bs_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/dragHandle"
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:clickable="true"
|
||||
app:menu="@menu/nav_bottom"
|
||||
tools:ignore="KeyboardInaccessibleWidget" />
|
||||
tools:ignore="KeyboardInaccessibleWidget"
|
||||
tools:menu="@menu/nav_bottom" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
tools:background="@color/grey" />
|
||||
|
||||
<org.koitharu.kotatsu.reader.ui.ReaderInfoBarView
|
||||
android:id="@+id/infoBar"
|
||||
@@ -25,6 +26,7 @@
|
||||
android:id="@+id/appbar_top"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?m3ColorBottomMenuBackground"
|
||||
android:elevation="@dimen/m3_card_elevated_elevation"
|
||||
app:elevation="@dimen/m3_card_elevated_elevation"
|
||||
app:liftOnScroll="false">
|
||||
@@ -46,6 +48,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_margin="8dp"
|
||||
app:cardBackgroundColor="?m3ColorBottomMenuBackground"
|
||||
app:layout_insetEdge="bottom">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
@@ -60,6 +63,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:stepSize="1"
|
||||
android:valueFrom="0"
|
||||
app:trackColorInactive="?attr/m3ColorBackground"
|
||||
app:labelBehavior="floating"
|
||||
app:tickVisible="false" />
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
style="?materialCardViewFilledStyle"
|
||||
android:layout_width="@dimen/widget_cover_width"
|
||||
android:layout_height="@dimen/widget_cover_height"
|
||||
android:focusableInTouchMode="false">
|
||||
android:focusableInTouchMode="false"
|
||||
app:strokeColor="?colorOutline">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
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"
|
||||
style="@style/Widget.Material3.CardView.Outlined"
|
||||
style="?materialCardViewOutlinedStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?scrimBackground">
|
||||
app:cardBackgroundColor="?colorBackgroundFloating">
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.CoverImageView
|
||||
android:id="@+id/imageView_thumb"
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dp"
|
||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
|
||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer"
|
||||
tools:layout_width="116dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rootLayout"
|
||||
@@ -13,14 +14,15 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/bg_appwidget_card"
|
||||
android:clipToOutline="true"
|
||||
android:foreground="?android:selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="4dp"
|
||||
android:outlineProvider="background"
|
||||
tools:ignore="UnusedAttribute,UselessParent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="@dimen/widget_cover_width"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/widget_cover_height"
|
||||
android:scaleType="centerCrop"
|
||||
tools:ignore="ContentDescription"
|
||||
@@ -33,8 +35,11 @@
|
||||
android:elegantTextHeight="false"
|
||||
android:ellipsize="end"
|
||||
android:lines="2"
|
||||
android:padding="2dp"
|
||||
android:paddingHorizontal="6dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="12sp"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
app:shapeAppearance="?shapeAppearanceCornerExtraLarge"
|
||||
tools:icon="@drawable/ic_notification"
|
||||
tools:primaryButtonText="@string/settings"
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:clipToPadding="false"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:scrollbars="none"
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:clipToOutline="true"
|
||||
android:outlineProvider="background"
|
||||
android:padding="4dp"
|
||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
|
||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer"
|
||||
tools:background="@drawable/bg_appwidget_root">
|
||||
|
||||
<StackView
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/stackView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -23,4 +25,4 @@
|
||||
android:text="@string/history_is_empty"
|
||||
android:textColor="?android:textColorPrimary" />
|
||||
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:padding="4dp"
|
||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
|
||||
android:clipToOutline="true"
|
||||
android:outlineProvider="background"
|
||||
android:theme="@style/Theme.Kotatsu.AppWidgetContainer"
|
||||
tools:background="?android:attr/colorBackground">
|
||||
|
||||
<GridView
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/gridView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:columnWidth="92dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:numColumns="auto_fit"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp"
|
||||
tools:listitem="@layout/item_shelf" />
|
||||
|
||||
<TextView
|
||||
@@ -27,4 +31,4 @@
|
||||
android:text="@string/you_have_not_favourites_yet"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
25
app/src/main/res/menu/nav_bottom_alt.xml
Normal file
25
app/src/main/res/menu/nav_bottom_alt.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_favourites"
|
||||
android:icon="@drawable/ic_favourites_selector"
|
||||
android:title="@string/favourites" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_history"
|
||||
android:icon="@drawable/ic_history_selector"
|
||||
android:title="@string/history" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_explore"
|
||||
android:icon="@drawable/ic_explore_selector"
|
||||
android:title="@string/explore" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_feed"
|
||||
android:icon="@drawable/ic_feed_selector"
|
||||
android:title="@string/feed" />
|
||||
|
||||
</menu>
|
||||
@@ -273,7 +273,7 @@
|
||||
<string name="show_reading_indicators_summary">Паказваць працэнт прачытанага ў гісторыі і абраных</string>
|
||||
<string name="data_deletion">Выдаленне даных</string>
|
||||
<string name="show_all">Паказаць усе</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Манга, пазначаная як NSFW, ніколі не будзе дададзеная ў гісторыю і ваш прагрэс не будзе захаваны</string>
|
||||
<string name="exclude_nsfw_from_history_summary">Манга, пазначаная як NSFW, не будзе дададзеная ў гісторыю і ваш прагрэс не будзе захаваны</string>
|
||||
<string name="clear_cookies_summary">Можа дапамагчы з некаторымі праблемам. Усе аўтарызацыі будуць ануляваныя</string>
|
||||
<string name="not_found_404">Змесціва не знойдзена ці выдалена</string>
|
||||
<string name="status_re_reading">Перачытваю</string>
|
||||
@@ -324,7 +324,7 @@
|
||||
<string name="discard">Адмяніць</string>
|
||||
<string name="enable_logging">Уключыць запіс</string>
|
||||
<string name="share_logs">Падзяліцца логамі</string>
|
||||
<string name="enable_logging_summary">Запішыце некаторыя дзеянні для адладкі</string>
|
||||
<string name="enable_logging_summary">Запішыце некаторыя дзеянні для адладкі. Уключайце толькі калі ведаеце, што робіце</string>
|
||||
<string name="show_suspicious_content">Паказаць падазроны кантэнт</string>
|
||||
<string name="text_shelf_holder_primary">Ваша манга будзе адлюстроўвацца тут</string>
|
||||
<string name="text_shelf_holder_secondary">Знайдзіце, што пачытаць, у раздзеле «Агляд»</string>
|
||||
@@ -368,7 +368,7 @@
|
||||
<string name="options">Параметры</string>
|
||||
<string name="user_agent">Загаловак UserAgent</string>
|
||||
<string name="allow_unstable_updates">Дазволіць нестабільныя абнаўленні</string>
|
||||
<string name="allow_unstable_updates_summary">Прапануйце абнаўленні да бэта-версій праграмы</string>
|
||||
<string name="allow_unstable_updates_summary">Атрымліваць апавяшчэнні аб нестабільных зборках</string>
|
||||
<string name="download_started">Спампоўка пачалася</string>
|
||||
<string name="settings_apply_restart_required">Калі ласка, перазапусціце праграму каб прымяніць змены</string>
|
||||
<string name="got_it">Зразумеў</string>
|
||||
@@ -383,7 +383,7 @@
|
||||
<string name="sync_settings">Налады сінхранізацыі</string>
|
||||
<string name="server_address">Адрас сервера</string>
|
||||
<string name="mirror_switching">Выберыце люстэрка аўтаматычна</string>
|
||||
<string name="mirror_switching_summary">Аўтаматычна пераключаць дамены для аддаленых крыніц у выпадку памылак, калі даступныя люстэркі</string>
|
||||
<string name="mirror_switching_summary">Аўтаматычна пераключаць дамены для крыніц мангі ў выпадку памылак, калі даступныя люстэркі</string>
|
||||
<string name="ignore_ssl_errors">Ігнараваць памылкі SSL</string>
|
||||
<string name="resume">Аднавіць</string>
|
||||
<string name="paused">Прыпынена</string>
|
||||
@@ -466,4 +466,9 @@
|
||||
<string name="languages">Мовы</string>
|
||||
<string name="unknown">Невядомы</string>
|
||||
<string name="in_progress">У працэсе</string>
|
||||
<string name="disable_nsfw">Адключыць NSFW</string>
|
||||
<string name="too_many_requests_message">Занадта шмат запытаў. Паўтарыце спробу пазней</string>
|
||||
<string name="related_manga_summary">Паказаць спіс звязанай мангі. У некаторых выпадках ён можа быць недакладным або адсутнічаць</string>
|
||||
<string name="advanced">Пашыраныя</string>
|
||||
<string name="default_section">Раздзел па змаўчанні</string>
|
||||
</resources>
|
||||
@@ -350,7 +350,7 @@
|
||||
<string name="mark_as_current">Marcar como actual</string>
|
||||
<string name="language">Idioma</string>
|
||||
<string name="share_logs">Compartir los registros</string>
|
||||
<string name="enable_logging_summary">Grabar algunas acciones para depurar</string>
|
||||
<string name="enable_logging_summary">Grabar algunas interaciones para de depurar los fallos. No lo actives si no estás seguro de lo que estás haciendo</string>
|
||||
<string name="enable_logging">Activar el registro</string>
|
||||
<string name="show_suspicious_content">Mostrar contenido sospechoso</string>
|
||||
<string name="theme_name_dynamic">Dinámico</string>
|
||||
@@ -366,7 +366,7 @@
|
||||
<string name="theme_name_kanade">Canadé</string>
|
||||
<string name="scrobbling_empty_hint">Para realizar un seguimiento del progreso de la lectura, seleccione Menú → Seguimiento en la pantalla de detalles del manga.</string>
|
||||
<string name="services">Servicios</string>
|
||||
<string name="allow_unstable_updates_summary">Actualizaciones propuestas para las versiones beta de la aplicación</string>
|
||||
<string name="allow_unstable_updates_summary">Recibir notificaciones sobre versiones inestables</string>
|
||||
<string name="allow_unstable_updates">Permitir actualizaciones inestables</string>
|
||||
<string name="download_started">Descarga iniciada</string>
|
||||
<string name="user_agent">Encabezado del agente de usuario</string>
|
||||
@@ -469,4 +469,7 @@
|
||||
<string name="disable_nsfw">Desactivar NSFW</string>
|
||||
<string name="too_many_requests_message">Demasiadas solicitudes. Vuelve a intentarlo más tarde</string>
|
||||
<string name="related_manga_summary">Muestra una lista de mangas relacionados. En algunos casos puede ser inexacta o faltar</string>
|
||||
<string name="advanced">Avanzado</string>
|
||||
<string name="default_section">Sección predeterminada</string>
|
||||
<string name="manga_list">Lista de mangas</string>
|
||||
</resources>
|
||||
@@ -350,7 +350,7 @@
|
||||
<string name="mark_as_current">Marquer comme actuel</string>
|
||||
<string name="share_logs">Partager les journaux</string>
|
||||
<string name="enable_logging">Activer la journalisation</string>
|
||||
<string name="enable_logging_summary">Enregistrer certaines actions à des fins de débogage</string>
|
||||
<string name="enable_logging_summary">Enregistrer quelques actions à des fins de débogage. Ne l\'activez pas si vous ne savez pas ce que vous faites</string>
|
||||
<string name="language">Langue</string>
|
||||
<string name="show_suspicious_content">Afficher le contenu suspect</string>
|
||||
<string name="theme_name_dynamic">Dynamique</string>
|
||||
@@ -368,7 +368,7 @@
|
||||
<string name="allow_unstable_updates">Autoriser les mises à jour instables</string>
|
||||
<string name="download_started">Téléchargement commencé</string>
|
||||
<string name="theme_name_kanade">Kanade</string>
|
||||
<string name="allow_unstable_updates_summary">Proposer des mises à jour des versions bêta de l\'application</string>
|
||||
<string name="allow_unstable_updates_summary">Recevoir des notifications sur les moutures instables</string>
|
||||
<string name="user_agent">En-tête UserAgent</string>
|
||||
<string name="settings_apply_restart_required">Veuillez redémarrer l\'application pour appliquer ces changements</string>
|
||||
<string name="got_it">Compris</string>
|
||||
@@ -389,7 +389,7 @@
|
||||
<string name="sync_settings">Paramètres de synchronisation</string>
|
||||
<string name="ignore_ssl_errors">Ignorer les erreurs SSL</string>
|
||||
<string name="mirror_switching">Choisir le miroir automatiquement</string>
|
||||
<string name="mirror_switching_summary">Changement automatique de domaine pour les sources distantes en cas d\'erreur si des miroirs sont disponibles</string>
|
||||
<string name="mirror_switching_summary">Changement automatique de domaine pour les sources de mangas en cas d\'erreur si des miroirs sont disponibles</string>
|
||||
<string name="pause">Mettre en pause</string>
|
||||
<string name="resume">Reprendre</string>
|
||||
<string name="paused">En pause</string>
|
||||
@@ -466,4 +466,7 @@
|
||||
<string name="languages">Langues</string>
|
||||
<string name="unknown">Inconnu</string>
|
||||
<string name="in_progress">En cours</string>
|
||||
<string name="disable_nsfw">Désactiver NSFW</string>
|
||||
<string name="too_many_requests_message">Trop de requêtes. Réessayez plus tard</string>
|
||||
<string name="related_manga_summary">Afficher une liste de mangas connexes. Dans certains cas, elle peut être inexacte ou manquante</string>
|
||||
</resources>
|
||||
@@ -345,7 +345,7 @@
|
||||
<string name="share_logs">Bagikan log</string>
|
||||
<string name="error_no_space_left">Tidak ada ruang tersisa di perangkat</string>
|
||||
<string name="allow_unstable_updates">Izinkan pembaruan yang tidak stabil</string>
|
||||
<string name="allow_unstable_updates_summary">Usulkan pembaruan ke versi beta</string>
|
||||
<string name="allow_unstable_updates_summary">Terima pemberitahuan tentang build yang tidak stabil</string>
|
||||
<string name="download_started">Unduh dimulai</string>
|
||||
<string name="show_reading_indicators">Tampilkan indikator kemajuan membaca</string>
|
||||
<string name="show_reading_indicators_summary">Tampilkan persentase baca dalam riwayat dan favorit</string>
|
||||
@@ -354,7 +354,7 @@
|
||||
<string name="zoom_mode_keep_start">Tetap di awal</string>
|
||||
<string name="reader_info_pattern">Bab. %1$d/%2$d Hal. %3$d/%4$d</string>
|
||||
<string name="enable_logging">Aktifkan pencatatan</string>
|
||||
<string name="enable_logging_summary">Rekam beberapa tindakan untuk tujuan debug</string>
|
||||
<string name="enable_logging_summary">Rekam beberapa tindakan untuk tujuan debug. Jangan nyalakan jika Anda tidak yakin dengan apa yang Anda lakukan</string>
|
||||
<string name="theme_name_dynamic">Dinamis</string>
|
||||
<string name="color_theme">Skema warna</string>
|
||||
<string name="show_in_grid_view">Perlihatkan dalam tampilan kisi</string>
|
||||
@@ -469,4 +469,7 @@
|
||||
<string name="languages">Bahasa</string>
|
||||
<string name="unknown">Tidak diketahui</string>
|
||||
<string name="in_progress">Dalam progres</string>
|
||||
<string name="advanced">Lanjutan</string>
|
||||
<string name="default_section">Bagian default</string>
|
||||
<string name="manga_list">Daftar manga</string>
|
||||
</resources>
|
||||
@@ -36,6 +36,9 @@
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOutline">#899391</item>
|
||||
<item name="colorOutlineVariant">#3F4947</item>
|
||||
<item name="m3ColorBackground">@color/background_miku</item>
|
||||
<item name="m3ColorExploreButton">#1D2020</item>
|
||||
<item name="m3ColorBottomMenuBackground">#272B2A</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Asuka">
|
||||
@@ -73,6 +76,9 @@
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOutline">#A08C8B</item>
|
||||
<item name="colorOutlineVariant">#534342</item>
|
||||
<item name="m3ColorBackground">@color/background_asuka</item>
|
||||
<item name="m3ColorExploreButton">#251E1E</item>
|
||||
<item name="m3ColorBottomMenuBackground">#2F2828</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Mion">
|
||||
@@ -110,6 +116,9 @@
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOutline">#8A938C</item>
|
||||
<item name="colorOutlineVariant">#404943</item>
|
||||
<item name="m3ColorBackground">@color/background_mion</item>
|
||||
<item name="m3ColorExploreButton">#1D201E</item>
|
||||
<item name="m3ColorBottomMenuBackground">#272B28</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Rikka">
|
||||
@@ -147,6 +156,9 @@
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOutline">#9F8C8C</item>
|
||||
<item name="colorOutlineVariant">#524343</item>
|
||||
<item name="m3ColorBackground">@color/background_rikka</item>
|
||||
<item name="m3ColorExploreButton">#241E1E</item>
|
||||
<item name="m3ColorBottomMenuBackground">#2F2828</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Sakura">
|
||||
@@ -184,6 +196,9 @@
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOutline">#899296</item>
|
||||
<item name="colorOutlineVariant">#40484B</item>
|
||||
<item name="m3ColorBackground">@color/background_sakura</item>
|
||||
<item name="m3ColorExploreButton">#1D2021</item>
|
||||
<item name="m3ColorBottomMenuBackground">#272A2C</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Mamimi">
|
||||
@@ -221,6 +236,9 @@
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOutline">#9F8C8D</item>
|
||||
<item name="colorOutlineVariant">#524344</item>
|
||||
<item name="m3ColorBackground">@color/background_mamimi</item>
|
||||
<item name="m3ColorExploreButton">#241E1E</item>
|
||||
<item name="m3ColorBottomMenuBackground">#2F2828</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Kotatsu.Kanade">
|
||||
@@ -258,5 +276,8 @@
|
||||
<item name="colorOnErrorContainer">#FFDAD6</item>
|
||||
<item name="colorOutline">#959595</item>
|
||||
<item name="colorOutlineVariant">#494949</item>
|
||||
<item name="m3ColorBackground">@color/background_kanade</item>
|
||||
<item name="m3ColorExploreButton">#292929</item>
|
||||
<item name="m3ColorBottomMenuBackground">#2C2C2C</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
<color name="splash_primary">#0C4091</color>
|
||||
|
||||
<!-- Color schemes colors -->
|
||||
<color name="background_miku">#272B2A</color>
|
||||
<color name="background_asuka">#2F2828</color>
|
||||
<color name="background_mion">#272B28</color>
|
||||
<color name="background_rikka">#2F2828</color>
|
||||
<color name="background_sakura">#272A2C</color>
|
||||
<color name="background_mamimi">#2F2828</color>
|
||||
<color name="background_kanade">#2B2B2B</color>
|
||||
<color name="background_miku">#191C1C</color>
|
||||
<color name="background_asuka">#201A1A</color>
|
||||
<color name="background_mion">#191C1A</color>
|
||||
<color name="background_rikka">#201A1A</color>
|
||||
<color name="background_sakura">#191C1D</color>
|
||||
<color name="background_mamimi">#201A1A</color>
|
||||
<color name="background_kanade">#222222</color>
|
||||
</resources>
|
||||
|
||||
@@ -44,5 +44,4 @@
|
||||
<color name="kotatsu_m3_background">#1A1B1F</color>
|
||||
<color name="kotatsu_m3_exploreButton">#292A2D</color>
|
||||
<color name="kotatsu_m3_bottom_menu">#2F3033</color>
|
||||
<color name="kotatsu_m3_content_scrim">#2F3033</color>
|
||||
</resources>
|
||||
|
||||
@@ -1,33 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d nowy rozdział</item>
|
||||
<item quantity="few">%1$d nowe rozdziały</item>
|
||||
<item quantity="many">%1$d nowych rozdziałów</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$d minutę temu</item>
|
||||
<item quantity="few">%1$d minuty temu</item>
|
||||
<item quantity="many">%1$d minut temu</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$d godzinę temu</item>
|
||||
<item quantity="few">%1$d godziny temu</item>
|
||||
<item quantity="many">%1$d godzin temu</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d dzień temu</item>
|
||||
<item quantity="few">%1$d dni temu</item>
|
||||
<item quantity="many">%1$d dni temu</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d przedmiot</item>
|
||||
<item quantity="few">%1$d przedmioty</item>
|
||||
<item quantity="many">%1$d przedmiotów</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d rozdział</item>
|
||||
<item quantity="few">%1$d rozdziały</item>
|
||||
<item quantity="many">%1$d rozdziałów</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
<plurals name="new_chapters">
|
||||
<item quantity="one">%1$d nowy rozdział</item>
|
||||
<item quantity="few">%1$d nowe rozdziały</item>
|
||||
<item quantity="many">%1$d nowych rozdziałów</item>
|
||||
</plurals>
|
||||
<plurals name="minutes_ago">
|
||||
<item quantity="one">%1$d minutę temu</item>
|
||||
<item quantity="few">%1$d minuty temu</item>
|
||||
<item quantity="many">%1$d minut temu</item>
|
||||
</plurals>
|
||||
<plurals name="hours_ago">
|
||||
<item quantity="one">%1$d godzinę temu</item>
|
||||
<item quantity="few">%1$d godziny temu</item>
|
||||
<item quantity="many">%1$d godzin temu</item>
|
||||
</plurals>
|
||||
<plurals name="days_ago">
|
||||
<item quantity="one">%1$d dzień temu</item>
|
||||
<item quantity="few">%1$d dni temu</item>
|
||||
<item quantity="many">%1$d dni temu</item>
|
||||
</plurals>
|
||||
<plurals name="items">
|
||||
<item quantity="one">%1$d przedmiot</item>
|
||||
<item quantity="few">%1$d przedmioty</item>
|
||||
<item quantity="many">%1$d przedmiotów</item>
|
||||
</plurals>
|
||||
<plurals name="chapters">
|
||||
<item quantity="one">%1$d rozdział</item>
|
||||
<item quantity="few">%1$d rozdziały</item>
|
||||
<item quantity="many">%1$d rozdziałów</item>
|
||||
</plurals>
|
||||
<plurals name="months_ago">
|
||||
<item quantity="one">%1$d miesiąc temu</item>
|
||||
<item quantity="few">%1$d miesiące temu</item>
|
||||
<item quantity="many">%1$d miesięcy temu</item>
|
||||
<item quantity="other">%1$d miesięcy temu</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
@@ -443,4 +443,17 @@
|
||||
<string name="pick_custom_directory">Wybierz katalog niestandardowy</string>
|
||||
<string name="no_access_to_file">Nie masz dostępu do tego pliku lub katalogu</string>
|
||||
<string name="local_manga_directories">Lokalne katalogi mangi</string>
|
||||
<string name="advanced">Zaawansowane</string>
|
||||
<string name="this_month">Ten miesiąc</string>
|
||||
<string name="voice_search">Wyszukiwanie głosowe</string>
|
||||
<string name="color_light">Jasny</string>
|
||||
<string name="color_dark">Ciemny</string>
|
||||
<string name="color_white">Biały</string>
|
||||
<string name="color_black">Czarny</string>
|
||||
<string name="background">Tło</string>
|
||||
<string name="progress">Postęp</string>
|
||||
<string name="order_added">Dodano</string>
|
||||
<string name="description">Opis</string>
|
||||
<string name="languages">Języki</string>
|
||||
<string name="unknown">Nieznane</string>
|
||||
</resources>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user