diff --git a/app/build.gradle b/app/build.gradle index 77fb6af12..4e4dd1fbe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 34 - versionCode = 639 - versionName = '7.0-rc2' + versionCode = 640 + versionName = '7.0-rc3' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { @@ -87,8 +87,8 @@ dependencies { } coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' - implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.23' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0' + implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.9.24' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1' implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.core:core-ktx:1.13.1' @@ -151,14 +151,14 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20240303' - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1' androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.test:core-ktx:1.5.0' androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5' - androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0' + androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1' androidTestImplementation 'androidx.room:room-testing:2.6.1' androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.15.1' diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt index cc20b8ecb..ea6d2db10 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CaptchaNotifier.kt @@ -36,7 +36,7 @@ class CaptchaNotifier( .build() manager.createNotificationChannel(channel) - val intent = CloudFlareActivity.newIntent(context, exception.url, exception.headers) + val intent = CloudFlareActivity.newIntent(context, exception) .setData(exception.url.toUri()) val notification = NotificationCompat.Builder(context, CHANNEL_ID) .setContentTitle(channel.name) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt index d56332ae3..68daa3c2d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/browser/cloudflare/CloudFlareActivity.kt @@ -23,12 +23,15 @@ import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.koitharu.kotatsu.R import org.koitharu.kotatsu.browser.WebViewBackPressedCallback +import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException +import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.util.TaggedActivityResult import org.koitharu.kotatsu.core.util.ext.configureForParser import org.koitharu.kotatsu.databinding.ActivityBrowserBinding +import org.koitharu.kotatsu.parsers.model.MangaSource import javax.inject.Inject import com.google.android.material.R as materialR @@ -137,6 +140,10 @@ class CloudFlareActivity : BaseActivity(), CloudFlareCal override fun onCheckPassed() { pendingResult = RESULT_OK + val source = intent?.getStringExtra(ARG_SOURCE) + if (source != null) { + CaptchaNotifier(this).dismiss(MangaSource(source)) + } finishAfterTransition() } @@ -174,9 +181,9 @@ class CloudFlareActivity : BaseActivity(), CloudFlareCal } } - class Contract : ActivityResultContract, TaggedActivityResult>() { - override fun createIntent(context: Context, input: Pair): Intent { - return newIntent(context, input.first, input.second) + class Contract : ActivityResultContract() { + override fun createIntent(context: Context, input: CloudFlareProtectedException): Intent { + return newIntent(context, input) } override fun parseResult(resultCode: Int, intent: Intent?): TaggedActivityResult { @@ -188,13 +195,23 @@ class CloudFlareActivity : BaseActivity(), CloudFlareCal const val TAG = "CloudFlareActivity" private const val ARG_UA = "ua" + private const val ARG_SOURCE = "_source" + + fun newIntent(context: Context, exception: CloudFlareProtectedException) = newIntent( + context = context, + url = exception.url, + source = exception.source, + headers = exception.headers, + ) - fun newIntent( + private fun newIntent( context: Context, url: String, + source: MangaSource?, headers: Headers?, ) = Intent(context, CloudFlareActivity::class.java).apply { data = url.toUri() + putExtra(ARG_SOURCE, source?.name) headers?.get(CommonHeaders.USER_AGENT)?.let { putExtra(ARG_UA, it) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt index 37cd49588..a9a9e1f02 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt @@ -20,8 +20,8 @@ abstract class MangaSourcesDao { @Query("SELECT * FROM sources ORDER BY sort_key") abstract suspend fun findAll(): List - @Query("SELECT * FROM sources WHERE enabled = 0 ORDER BY sort_key") - abstract suspend fun findAllDisabled(): List + @Query("SELECT source FROM sources WHERE enabled = 1") + abstract suspend fun findAllEnabledNames(): List @Query("SELECT * FROM sources ORDER BY sort_key") abstract fun observeAll(): Flow> diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt index c6a6d04d8..3f605c9a9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/resolve/ExceptionResolver.kt @@ -6,7 +6,6 @@ import androidx.annotation.StringRes import androidx.collection.ArrayMap import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity -import okhttp3.Headers import org.koitharu.kotatsu.R import org.koitharu.kotatsu.alternatives.ui.AlternativesActivity import org.koitharu.kotatsu.browser.BrowserActivity @@ -30,7 +29,7 @@ class ExceptionResolver : ActivityResultCallback { private val activity: FragmentActivity? private val fragment: Fragment? private val sourceAuthContract: ActivityResultLauncher - private val cloudflareContract: ActivityResultLauncher> + private val cloudflareContract: ActivityResultLauncher constructor(activity: FragmentActivity) { this.activity = activity @@ -55,7 +54,7 @@ class ExceptionResolver : ActivityResultCallback { } suspend fun resolve(e: Throwable): Boolean = when (e) { - is CloudFlareProtectedException -> resolveCF(e.url, e.headers) + is CloudFlareProtectedException -> resolveCF(e) is AuthRequiredException -> resolveAuthException(e.source) is NotFoundException -> { openInBrowser(e.url) @@ -70,9 +69,9 @@ class ExceptionResolver : ActivityResultCallback { else -> false } - private suspend fun resolveCF(url: String, headers: Headers): Boolean = suspendCoroutine { cont -> + private suspend fun resolveCF(e: CloudFlareProtectedException): Boolean = suspendCoroutine { cont -> continuations[CloudFlareActivity.TAG] = cont - cloudflareContract.launch(url to headers) + cloudflareContract.launch(e) } private suspend fun resolveAuthException(source: MangaSource): Boolean = suspendCoroutine { cont -> diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt index 5db319d49..986855095 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/os/AppShortcutManager.kt @@ -32,6 +32,7 @@ import org.koitharu.kotatsu.core.util.ext.source import org.koitharu.kotatsu.history.data.HistoryRepository import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.mapNotNullToSet import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.reader.ui.ReaderActivity import org.koitharu.kotatsu.search.ui.MangaListActivity @@ -91,6 +92,14 @@ class AppShortcutManager @Inject constructor( false } + fun getMangaShortcuts(): Set { + val shortcuts = ShortcutManagerCompat.getShortcuts( + context, + ShortcutManagerCompat.FLAG_MATCH_CACHED or ShortcutManagerCompat.FLAG_MATCH_PINNED or ShortcutManagerCompat.FLAG_MATCH_DYNAMIC, + ) + return shortcuts.mapNotNullToSet { it.id.toLongOrNull() } + } + @VisibleForTesting suspend fun await(): Boolean { return shortcutsUpdateJob?.join() != null diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt index 815290238..852bdace1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt @@ -22,11 +22,11 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.network.UserAgents -import org.koitharu.kotatsu.parsers.util.SuspendLazy import java.lang.ref.WeakReference import java.util.Locale import javax.inject.Inject import javax.inject.Singleton +import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -38,15 +38,10 @@ class MangaLoaderContextImpl @Inject constructor( ) : MangaLoaderContext() { private var webViewCached: WeakReference? = null - - private val userAgentLazy = SuspendLazy { - withContext(Dispatchers.Main) { - obtainWebView().settings.userAgentString - }.sanitizeHeaderValue() - } + private val webViewUserAgent by lazy { obtainWebViewUserAgent() } @SuppressLint("SetJavaScriptEnabled") - override suspend fun evaluateJs(script: String): String? = withContext(Dispatchers.Main) { + override suspend fun evaluateJs(script: String): String? = withContext(Dispatchers.Main.immediate) { val webView = obtainWebView() suspendCoroutine { cont -> webView.evaluateJavascript(script) { result -> @@ -55,13 +50,7 @@ class MangaLoaderContextImpl @Inject constructor( } } - override fun getDefaultUserAgent(): String = runCatching { - runBlocking { - userAgentLazy.get() - } - }.onFailure { e -> - e.printStackTraceDebug() - }.getOrDefault(UserAgents.FIREFOX_MOBILE) + override fun getDefaultUserAgent(): String = webViewUserAgent override fun getConfig(source: MangaSource): MangaSourceConfig { return SourceSettings(androidContext, source) @@ -86,4 +75,22 @@ class MangaLoaderContextImpl @Inject constructor( webViewCached = WeakReference(it) } } + + private fun obtainWebViewUserAgent(): String { + val mainDispatcher = Dispatchers.Main.immediate + return if (!mainDispatcher.isDispatchNeeded(EmptyCoroutineContext)) { + obtainWebViewUserAgentImpl() + } else { + runBlocking(mainDispatcher) { + obtainWebViewUserAgentImpl() + } + } + } + + @MainThread + private fun obtainWebViewUserAgentImpl() = runCatching { + obtainWebView().settings.userAgentString.sanitizeHeaderValue() + }.onFailure { e -> + e.printStackTraceDebug() + }.getOrDefault(UserAgents.FIREFOX_MOBILE) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt index 970be2aa8..7d7e78fa6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/RemoteMangaRepository.kt @@ -38,7 +38,7 @@ import java.util.Locale class RemoteMangaRepository( private val parser: MangaParser, - private val cache: MemoryContentCache, // TODO fix concurrency + private val cache: MemoryContentCache, private val mirrorSwitchInterceptor: MirrorSwitchInterceptor, ) : MangaRepository, Interceptor { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CardDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CardDrawable.kt deleted file mode 100644 index 6b85d00d5..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CardDrawable.kt +++ /dev/null @@ -1,162 +0,0 @@ -package org.koitharu.kotatsu.core.ui.image - -import android.content.Context -import android.content.res.ColorStateList -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.ColorFilter -import android.graphics.Outline -import android.graphics.Paint -import android.graphics.Path -import android.graphics.PixelFormat -import android.graphics.Rect -import android.graphics.RectF -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.os.Build -import androidx.annotation.ReturnThis -import org.koitharu.kotatsu.core.util.ext.getThemeColorStateList -import org.koitharu.kotatsu.core.util.ext.resolveDp -import org.koitharu.kotatsu.parsers.util.toIntUp -import com.google.android.material.R as materialR - -class CardDrawable( - context: Context, - private var corners: Int, -) : Drawable() { - - private val cornerSize = context.resources.resolveDp(12f) - private val paint = Paint(Paint.ANTI_ALIAS_FLAG) - private val cornersF = FloatArray(8) - private val boundsF = RectF() - private val color: ColorStateList - private val path = Path() - private var alpha = 255 - private var state: IntArray? = null - private var horizontalInset: Int = 0 - - init { - paint.style = Paint.Style.FILL - color = context.getThemeColorStateList(materialR.attr.colorSurfaceContainerHighest) - ?: ColorStateList.valueOf(Color.TRANSPARENT) - setCorners(corners) - updateColor() - } - - override fun draw(canvas: Canvas) { - canvas.drawPath(path, paint) - } - - override fun setAlpha(alpha: Int) { - this.alpha = alpha - updateColor() - } - - override fun setColorFilter(colorFilter: ColorFilter?) { - paint.colorFilter = colorFilter - } - - override fun getColorFilter(): ColorFilter? = paint.colorFilter - - override fun getOutline(outline: Outline) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - outline.setPath(path) - } else if (path.isConvex) { - outline.setConvexPath(path) - } - outline.alpha = 1f - } - - override fun getPadding(padding: Rect): Boolean { - padding.set( - horizontalInset, - 0, - horizontalInset, - 0, - ) - if (corners or TOP != 0) { - padding.top += cornerSize.toIntUp() - } - if (corners or BOTTOM != 0) { - padding.bottom += cornerSize.toIntUp() - } - return horizontalInset != 0 - } - - override fun onStateChange(state: IntArray): Boolean { - this.state = state - if (color.isStateful) { - updateColor() - return true - } else { - return false - } - } - - @Deprecated("Deprecated in Java") - override fun getOpacity(): Int = PixelFormat.TRANSPARENT - - override fun onBoundsChange(bounds: Rect) { - super.onBoundsChange(bounds) - boundsF.set(bounds) - boundsF.inset(horizontalInset.toFloat(), 0f) - path.reset() - path.addRoundRect(boundsF, cornersF, Path.Direction.CW) - path.close() - } - - @ReturnThis - fun setCorners(corners: Int): CardDrawable { - this.corners = corners - val topLeft = if (corners and TOP_LEFT == TOP_LEFT) cornerSize else 0f - val topRight = if (corners and TOP_RIGHT == TOP_RIGHT) cornerSize else 0f - val bottomRight = if (corners and BOTTOM_RIGHT == BOTTOM_RIGHT) cornerSize else 0f - val bottomLeft = if (corners and BOTTOM_LEFT == BOTTOM_LEFT) cornerSize else 0f - cornersF[0] = topLeft - cornersF[1] = topLeft - cornersF[2] = topRight - cornersF[3] = topRight - cornersF[4] = bottomRight - cornersF[5] = bottomRight - cornersF[6] = bottomLeft - cornersF[7] = bottomLeft - invalidateSelf() - return this - } - - fun setHorizontalInset(inset: Int) { - horizontalInset = inset - invalidateSelf() - } - - private fun updateColor() { - paint.color = color.getColorForState(state, color.defaultColor) - paint.alpha = alpha - } - - companion object { - - const val TOP_LEFT = 1 - const val TOP_RIGHT = 2 - const val BOTTOM_LEFT = 4 - const val BOTTOM_RIGHT = 8 - - const val LEFT = TOP_LEFT or BOTTOM_LEFT - const val TOP = TOP_LEFT or TOP_RIGHT - const val RIGHT = TOP_RIGHT or BOTTOM_RIGHT - const val BOTTOM = BOTTOM_LEFT or BOTTOM_RIGHT - - const val NONE = 0 - const val ALL = TOP_LEFT or TOP_RIGHT or BOTTOM_RIGHT or BOTTOM_LEFT - - fun from(d: Drawable?): CardDrawable? = when (d) { - null -> null - is CardDrawable -> d - is LayerDrawable -> (0 until d.numberOfLayers).firstNotNullOfOrNull { i -> - from(d.getDrawable(i)) - } - - else -> null - } - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Display.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Display.kt deleted file mode 100644 index b8ca902d4..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Display.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.koitharu.kotatsu.core.util.ext - -import android.app.Activity -import android.graphics.Rect -import android.os.Build -import android.util.DisplayMetrics -import android.view.Display - -@Suppress("DEPRECATION") -val Activity.displayCompat: Display - get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - display ?: windowManager.defaultDisplay - } else { - windowManager.defaultDisplay - } - -fun Activity.getDisplaySize(): Rect { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - windowManager.currentWindowMetrics.bounds - } else { - val dm = DisplayMetrics() - displayCompat.getRealMetrics(dm) - Rect(0, 0, dm.widthPixels, dm.heightPixels) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/service/MangaPrefetchService.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/service/MangaPrefetchService.kt index 90cf74ef7..471f4aa32 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/service/MangaPrefetchService.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/service/MangaPrefetchService.kt @@ -110,10 +110,7 @@ class MangaPrefetchService : CoroutineIntentService() { } private fun isPrefetchAvailable(context: Context, source: MangaSource?): Boolean { - if (source == MangaSource.LOCAL) { - return false - } - if (context.isPowerSaveMode()) { + if (source == MangaSource.LOCAL || context.isPowerSaveMode()) { return false } val entryPoint = EntryPointAccessors.fromApplication( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 64d62bb12..bccdde97f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -173,7 +173,7 @@ class DetailsViewModel @Inject constructor( } else { emptyList() } - }.stateIn(viewModelScope, SharingStarted.Lazily, emptyList()) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, emptyList()) val branches: StateFlow> = combine( details, @@ -220,7 +220,7 @@ class DetailsViewModel @Inject constructor( chaptersQuery, ) { list, reversed, query -> (if (reversed) list.asReversed() else list).filterSearch(query) - }.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList()) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, emptyList()) val readingTime = combine( details, @@ -228,7 +228,7 @@ class DetailsViewModel @Inject constructor( history, ) { m, b, h -> readingTimeUseCase.invoke(m, b, h) - }.stateIn(viewModelScope, SharingStarted.Lazily, null) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, null) val selectedBranchValue: String? get() = selectedBranch.value diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt index 8844e3293..828f0cef2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsViewModel.kt @@ -322,7 +322,7 @@ class DownloadsViewModel @Inject constructor( emit(mapChapters()) } } - }.stateIn(viewModelScope, SharingStarted.Eagerly, null) + }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null) private suspend fun tryLoad(manga: Manga) = runCatchingCancellable { (mangaRepositoryFactory.create(manga.source) as RemoteMangaRepository).getDetails(manga) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt index 2e14ed118..48e01376b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt @@ -17,7 +17,6 @@ import org.koitharu.kotatsu.core.model.isNsfw import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.ui.util.ReversibleHandle -import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.mapToSet import java.util.Collections @@ -48,8 +47,17 @@ class MangaSourcesRepository @Inject constructor( return dao.findAllEnabled(order).toSources(settings.isNsfwContentDisabled, order) } - suspend fun getDisabledSources(): List { - return dao.findAllDisabled().toSources(settings.isNsfwContentDisabled, null) + suspend fun getDisabledSources(): Set { + val result = EnumSet.copyOf(remoteSources) + val enabled = dao.findAllEnabledNames() + for (name in enabled) { + val source = MangaSource(name) + result.remove(source) + } + if (settings.isNsfwContentDisabled) { + result.removeAll { it.isNsfw() } + } + return result } fun observeIsEnabled(source: MangaSource): Flow { @@ -143,6 +151,7 @@ class MangaSourcesRepository @Inject constructor( result }.distinctUntilChanged() } else { + assimilateNewSources() flowOf(emptySet()) } } @@ -199,7 +208,7 @@ class MangaSourcesRepository @Inject constructor( val result = ArrayList(size) for (entity in this) { val source = MangaSource(entity.source) - if (skipNsfwSources && source.contentType == ContentType.HENTAI) { + if (skipNsfwSources && source.isNsfw()) { continue } if (source in remoteSources) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index 0b9453e64..5733409c8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -17,11 +17,9 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import coil.ImageLoader import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch import org.koitharu.kotatsu.R import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver -import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog import org.koitharu.kotatsu.core.ui.list.ListSelectionController @@ -34,7 +32,6 @@ import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent -import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope import org.koitharu.kotatsu.databinding.FragmentExploreBinding import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.download.ui.list.DownloadsActivity @@ -64,9 +61,6 @@ class ExploreFragment : @Inject lateinit var coil: ImageLoader - @Inject - lateinit var shortcutManager: AppShortcutManager - private val viewModel by viewModels() private var exploreAdapter: ExploreAdapter? = null private var sourceSelectionController: ListSelectionController? = null @@ -213,9 +207,7 @@ class ExploreFragment : R.id.action_shortcut -> { val source = selectedSources.singleOrNull() ?: return false - viewLifecycleScope.launch { - shortcutManager.requestPinShortcut(source) - } + viewModel.requestPinShortcut(source) mode.finish() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt index 3083f30a2..ec8cc3da9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsStateFlow @@ -43,6 +44,7 @@ class ExploreViewModel @Inject constructor( private val suggestionRepository: SuggestionRepository, private val exploreRepository: ExploreRepository, private val sourcesRepository: MangaSourcesRepository, + private val shortcutManager: AppShortcutManager, ) : BaseViewModel() { val isGrid = settings.observeAsStateFlow( @@ -106,6 +108,12 @@ class ExploreViewModel @Inject constructor( } } + fun requestPinShortcut(source: MangaSource) { + launchLoadingJob(Dispatchers.Default) { + shortcutManager.requestPinShortcut(source) + } + } + fun respondSuggestionTip(isAccepted: Boolean) { settings.isSuggestionsEnabled = isAccepted settings.closeTip(TIP_SUGGESTIONS) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt index d8a2daa6a..951014329 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.favourites.ui.categories -import android.content.Context -import android.content.Intent import android.os.Bundle import android.view.View import android.view.ViewGroup @@ -177,10 +175,4 @@ class FavouriteCategoriesActivity : viewModel.saveOrder(adapter.items ?: return) } } - - @Deprecated("") - companion object { - - fun newIntent(context: Context) = Intent(context, FavouriteCategoriesActivity::class.java) - } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/CategoriesHeaderAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/CategoriesHeaderAD.kt index 2b7ad5686..c0ad9b772 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/CategoriesHeaderAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/CategoriesHeaderAD.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.favourites.ui.categories.select.adapter +import android.content.Intent import android.view.View import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R @@ -16,7 +17,7 @@ fun categoriesHeaderAD() = adapterDelegateViewBinding val intent = when (v.id) { R.id.chip_create -> FavouritesCategoryEditActivity.newIntent(v.context) - R.id.chip_manage -> FavouriteCategoriesActivity.newIntent(v.context) + R.id.chip_manage -> Intent(v.context, FavouriteCategoriesActivity::class.java) else -> return@OnClickListener } v.context.startActivity(intent) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt index bffd9cf52..bed90f277 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.favourites.ui.container +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -102,7 +103,7 @@ class FavouritesContainerFragment : BaseFragment startActivity( - FavouriteCategoriesActivity.newIntent(v.context), + Intent(v.context, FavouriteCategoriesActivity::class.java), ) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerMenuProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerMenuProvider.kt index 76f817c89..1605204e4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerMenuProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerMenuProvider.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.favourites.ui.container import android.content.Context +import android.content.Intent import android.view.Menu import android.view.MenuInflater import android.view.MenuItem @@ -19,7 +20,7 @@ class FavouritesContainerMenuProvider( override fun onMenuItemSelected(menuItem: MenuItem): Boolean { when (menuItem.itemId) { R.id.action_manage -> { - context.startActivity(FavouriteCategoriesActivity.newIntent(context)) + context.startActivity(Intent(context, FavouriteCategoriesActivity::class.java)) } else -> return false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt index b2a2c3155..c82a39e69 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/config/ListConfigViewModel.kt @@ -82,7 +82,7 @@ class ListConfigViewModel @Inject constructor( ListConfigSection.General -> null ListConfigSection.Updated -> null ListConfigSection.History -> settings.historySortOrder - ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE // TODO + ListConfigSection.Suggestions -> ListSortOrder.RELEVANCE } fun setSortOrder(position: Int) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/importer/SingleMangaImporter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/importer/SingleMangaImporter.kt index 02ef9409c..88ad215e5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/importer/SingleMangaImporter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/importer/SingleMangaImporter.kt @@ -72,9 +72,6 @@ class SingleMangaImporter @Inject constructor( return LocalMangaInput.of(dest).getManga() } - /** - * TODO: progress - */ private suspend fun DocumentFile.copyTo(destDir: File) { if (isDirectory) { val subDir = File(destDir, requireName()) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt index 480aaff69..3569959f0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/main/ui/MainActivity.kt @@ -45,7 +45,6 @@ import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.OptionsMenuBadgeHelper import org.koitharu.kotatsu.core.ui.widgets.SlidingBottomNavigationView import org.koitharu.kotatsu.core.util.ext.hideKeyboard -import org.koitharu.kotatsu.core.util.ext.measureHeight import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf @@ -77,7 +76,7 @@ private const val TAG_SEARCH = "search" class MainActivity : BaseActivity(), AppBarOwner, BottomNavOwner, View.OnClickListener, View.OnFocusChangeListener, SearchSuggestionListener, - MainNavigationDelegate.OnFragmentChangedListener { + MainNavigationDelegate.OnFragmentChangedListener, View.OnLayoutChangeListener { @Inject lateinit var settings: AppSettings @@ -136,6 +135,7 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav } viewModel.isBottomNavPinned.observe(this, ::setNavbarPinned) searchSuggestionViewModel.isIncognitoModeEnabled.observe(this, this::onIncognitoModeChanged) + viewBinding.bottomNav?.addOnLayoutChangeListener(this) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { @@ -211,6 +211,22 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav ) } + override fun onLayoutChange( + v: View?, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + if (top != oldTop || bottom != oldBottom) { + updateContainerBottomMargin() + } + } + override fun onFocusChange(v: View?, hasFocus: Boolean) { val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH) if (v?.id == R.id.searchView && hasFocus) { @@ -418,12 +434,17 @@ class MainActivity : BaseActivity(), AppBarOwner, BottomNav view.layoutParams = lp } } - viewBinding.container.updateLayoutParams { - bottomMargin = if (isPinned) { - (bottomNavBar?.measureHeight() - ?.coerceAtLeast(resources.getDimensionPixelSize(materialR.dimen.m3_bottom_nav_min_height)) ?: 0) - } else { - 0 + updateContainerBottomMargin() + } + + private fun updateContainerBottomMargin() { + val bottomNavBar = viewBinding.bottomNav ?: return + val newMargin = if (bottomNavBar.isPinned) bottomNavBar.height else 0 + with(viewBinding.container) { + val params = layoutParams as MarginLayoutParams + if (params.bottomMargin != newMargin) { + params.bottomMargin = newMargin + layoutParams = params } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageSnapHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageSnapHelper.kt index 024525222..cf9b4106b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageSnapHelper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/doublepage/DoublePageSnapHelper.kt @@ -4,7 +4,6 @@ import android.util.DisplayMetrics import android.view.View import android.view.animation.Interpolator import android.widget.Scroller -import androidx.core.view.ViewCompat import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller @@ -57,7 +56,7 @@ class DoublePageSnapHelper : SnapHelper() { val layoutManager = recyclerView.layoutManager as LinearLayoutManager check(layoutManager.canScrollHorizontally()) { "RecyclerView must be scrollable" } orientationHelper = OrientationHelper.createHorizontalHelper(layoutManager) - layoutDirectionHelper = LayoutDirectionHelper(ViewCompat.getLayoutDirection(recyclerView)) + layoutDirectionHelper = LayoutDirectionHelper(recyclerView.layoutDirection) scroller = Scroller(target.context, snapInterpolator) initItemDimensionIfNeeded(layoutManager) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt index 1223480e6..f39e5e0cd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/pager/webtoon/WebtoonScalingFrame.kt @@ -18,7 +18,6 @@ import android.view.animation.DecelerateInterpolator import android.widget.FrameLayout import android.widget.OverScroller import androidx.core.animation.doOnEnd -import androidx.core.view.GestureDetectorCompat import androidx.core.view.ViewConfigurationCompat import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.widgets.ZoomControl @@ -39,7 +38,7 @@ class WebtoonScalingFrame @JvmOverloads constructor( ZoomControl.ZoomControlListener { private val scaleDetector = ScaleGestureDetector(context, this) - private val gestureDetector = GestureDetectorCompat(context, GestureListener()) + private val gestureDetector = GestureDetector(context, GestureListener()) private val overScroller = OverScroller(context, AccelerateDecelerateInterpolator()) private val transformMatrix = Matrix() @@ -339,7 +338,7 @@ class WebtoonScalingFrame @JvmOverloads constructor( if (overScroller.computeScrollOffset()) { transformMatrix.postTranslate( overScroller.currX.toFloat() - prevPos.x, - overScroller.currY.toFloat() - prevPos.y + overScroller.currY.toFloat() - prevPos.y, ) prevPos.set(overScroller.currX, overScroller.currY) invalidateTarget() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt index b5ab50d02..c5fc090a1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt @@ -31,7 +31,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) { ScrobblerUser( id = lines[0].toLong(), nickname = lines[1], - avatar = lines[2], + avatar = lines[2].takeUnless(String::isEmpty), service = ScrobblerService.valueOf(lines[3]), ) } @@ -43,7 +43,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) { val str = StringJoiner("\n") .add(value.id) .add(value.nickname) - .add(value.avatar) + .add(value.avatar.orEmpty()) .add(value.service.name) .complete() putString(KEY_USER, str) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt index 736c5c5f4..07badad9b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt @@ -67,10 +67,8 @@ class ScrobblerConfigActivity : BaseActivity(), override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - if (intent != null) { - setIntent(intent) - processIntent(intent) - } + setIntent(intent) + processIntent(intent) } override fun onWindowInsetsChanged(insets: Insets) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/SplitSwitchPreference.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/SplitSwitchPreference.kt index 46bdd6795..70afdb0a4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/SplitSwitchPreference.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/utils/SplitSwitchPreference.kt @@ -20,7 +20,7 @@ class SplitSwitchPreference @JvmOverloads constructor( var onContainerClickListener: OnPreferenceClickListener? = null - private val containerClickListener = View.OnClickListener { v -> + private val containerClickListener = View.OnClickListener { onContainerClickListener?.onPreferenceClick(this) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/BarChartView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/BarChartView.kt index 73c8dc2e6..f413eadfd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/BarChartView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/BarChartView.kt @@ -1,35 +1,18 @@ package org.koitharu.kotatsu.stats.ui.views -import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas -import android.graphics.Color import android.graphics.DashPathEffect import android.graphics.Paint -import android.graphics.PathEffect -import android.graphics.PorterDuff -import android.graphics.PorterDuffXfermode import android.graphics.RectF -import android.graphics.Xfermode import android.util.AttributeSet -import android.view.GestureDetector -import android.view.MotionEvent import android.view.View import androidx.annotation.ColorInt -import androidx.collection.MutableIntList -import androidx.core.graphics.ColorUtils -import androidx.core.graphics.minus -import androidx.core.view.GestureDetectorCompat -import androidx.core.view.setPadding -import com.google.android.material.color.MaterialColors import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.parsers.util.replaceWith import org.koitharu.kotatsu.parsers.util.toIntUp -import kotlin.math.absoluteValue -import kotlin.math.max import kotlin.math.roundToInt -import kotlin.math.sqrt import kotlin.random.Random import com.google.android.material.R as materialR @@ -126,7 +109,7 @@ class BarChartView @JvmOverloads constructor( bars.clear() return } - var fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing + val fullWidth = rawData.size * (barWidth + minBarSpacing) + minBarSpacing val windowSize = (fullWidth / width.toFloat()).toIntUp() bars.replaceWith( rawData.chunked(windowSize) { it.average() }, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/PieChartView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/PieChartView.kt index 0f5cd770f..6f2cc29cb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/PieChartView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/views/PieChartView.kt @@ -3,28 +3,17 @@ package org.koitharu.kotatsu.stats.ui.views import android.annotation.SuppressLint import android.content.Context import android.graphics.Canvas -import android.graphics.Color import android.graphics.Paint -import android.graphics.PorterDuff -import android.graphics.PorterDuffXfermode import android.graphics.RectF -import android.graphics.Xfermode import android.util.AttributeSet import android.view.GestureDetector import android.view.MotionEvent import android.view.View -import androidx.annotation.ColorInt -import androidx.collection.MutableIntList import androidx.core.graphics.ColorUtils -import androidx.core.graphics.minus -import androidx.core.view.GestureDetectorCompat -import com.google.android.material.color.MaterialColors import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.resolveDp import org.koitharu.kotatsu.parsers.util.replaceWith -import kotlin.math.absoluteValue import kotlin.math.sqrt -import com.google.android.material.R as materialR class PieChartView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 @@ -34,8 +23,8 @@ class PieChartView @JvmOverloads constructor( private val segments = ArrayList() private val chartBounds = RectF() private val clearColor = context.getThemeColor(android.R.attr.colorBackground) - private val touchDetector = GestureDetectorCompat(context, this) - private var hightlightedSegment = -1 + private val touchDetector = GestureDetector(context, this) + private var highlightedSegment = -1 var onSegmentClickListener: OnSegmentClickListener? = null @@ -49,7 +38,7 @@ class PieChartView @JvmOverloads constructor( var angle = 0f for ((i, segment) in segments.withIndex()) { paint.color = segment.color - if (i == hightlightedSegment) { + if (i == highlightedSegment) { paint.color = ColorUtils.setAlphaComponent(paint.color, 180) } paint.style = Paint.Style.FILL @@ -91,7 +80,7 @@ class PieChartView @JvmOverloads constructor( @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { if (event.actionMasked == MotionEvent.ACTION_CANCEL || event.actionMasked == MotionEvent.ACTION_UP) { - hightlightedSegment = -1 + highlightedSegment = -1 invalidate() } return super.onTouchEvent(event) || touchDetector.onTouchEvent(event) @@ -102,8 +91,8 @@ class PieChartView @JvmOverloads constructor( return false } val segment = findSegmentIndex(e.x, e.y) - if (segment != hightlightedSegment) { - hightlightedSegment = segment + if (segment != highlightedSegment) { + highlightedSegment = segment invalidate() return true } else { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/sync/domain/SyncHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/sync/domain/SyncHelper.kt index 803d5a2d6..666476c48 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/sync/domain/SyncHelper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/sync/domain/SyncHelper.kt @@ -79,12 +79,10 @@ class SyncHelper @AssistedInject constructor( .build() val response = httpClient.newCall(request).execute().log().parseJsonOrNull() if (response != null) { - val timestamp = response.getLong(FIELD_TIMESTAMP) - val categoriesResult = - upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES), timestamp) + val categoriesResult = upsertFavouriteCategories(response.getJSONArray(TABLE_FAVOURITE_CATEGORIES)) stats.numDeletes += categoriesResult.first().count?.toLong() ?: 0L stats.numInserts += categoriesResult.drop(1).sumOf { it.count?.toLong() ?: 0L } - val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES), timestamp) + val favouritesResult = upsertFavourites(response.getJSONArray(TABLE_FAVOURITES)) stats.numDeletes += favouritesResult.first().count?.toLong() ?: 0L stats.numInserts += favouritesResult.drop(1).sumOf { it.count?.toLong() ?: 0L } stats.numEntries += stats.numInserts + stats.numDeletes @@ -105,7 +103,6 @@ class SyncHelper @AssistedInject constructor( if (response != null) { val result = upsertHistory( json = response.getJSONArray(TABLE_HISTORY), - timestamp = response.getLong(FIELD_TIMESTAMP), ) stats.numDeletes += result.first().count?.toLong() ?: 0L stats.numInserts += result.drop(1).sumOf { it.count?.toLong() ?: 0L } @@ -122,12 +119,12 @@ class SyncHelper @AssistedInject constructor( fun onSyncComplete(result: SyncResult) { if (logger.isEnabled) { - logger.log("Sync finshed: ${result.toDebugString()}") + logger.log("Sync finished: ${result.toDebugString()}") logger.flushBlocking() } } - private fun upsertHistory(json: JSONArray, timestamp: Long): Array { + private fun upsertHistory(json: JSONArray): Array { val uri = uri(authorityHistory, TABLE_HISTORY) val operations = ArrayList() json.mapJSONTo(operations) { jo -> @@ -139,7 +136,7 @@ class SyncHelper @AssistedInject constructor( return provider.applyBatch(operations) } - private fun upsertFavouriteCategories(json: JSONArray, timestamp: Long): Array { + private fun upsertFavouriteCategories(json: JSONArray): Array { val uri = uri(authorityFavourites, TABLE_FAVOURITE_CATEGORIES) val operations = ArrayList() json.mapJSONTo(operations) { jo -> @@ -150,7 +147,7 @@ class SyncHelper @AssistedInject constructor( return provider.applyBatch(operations) } - private fun upsertFavourites(json: JSONArray, timestamp: Long): Array { + private fun upsertFavourites(json: JSONArray): Array { val uri = uri(authorityFavourites, TABLE_FAVOURITES) val operations = ArrayList() json.mapJSONTo(operations) { jo -> diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt index c94e449b9..706b17f9a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/widget/recent/RecentWidgetProvider.kt @@ -31,6 +31,7 @@ class RecentWidgetProvider : BaseAppWidgetProvider() { } else { views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root) } + // TODO security 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)) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt b/app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt index 5246ecd3f..70d309018 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/widget/shelf/ShelfWidgetProvider.kt @@ -31,6 +31,7 @@ class ShelfWidgetProvider : BaseAppWidgetProvider() { } else { views.setInt(R.id.widget_root, "setBackgroundResource", R.drawable.bg_appwidget_root) } + // TODO security 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)) diff --git a/app/src/main/res/layout/activity_browser.xml b/app/src/main/res/layout/activity_browser.xml index 896c1ffe3..4e7f22043 100644 --- a/app/src/main/res/layout/activity_browser.xml +++ b/app/src/main/res/layout/activity_browser.xml @@ -21,7 +21,6 @@ android:layout_height="wrap_content" android:indeterminate="true" android:visibility="gone" - app:layout_constraintBottom_toBottomOf="@id/appbar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/appbar" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28ae25538..269873af7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,641 +1,641 @@ - Kotatsu - Local storage - Favourites - History - An error occurred - Network error - Details - Chapters - List - Detailed list - Grid - List mode - Settings - Manga sources - Loading… - Computing… - Chapter %1$d of %2$d - Close - Try again - Clear history - Nothing found - No history yet - Read - No favourites yet - Favourite this - New category - Add - Save - Share - Create shortcut… - Share %s - Search - Search manga - Downloading… - Processing… - Downloaded - Downloads - Name - Popular - Updated - Newest - Rating - Sorting order - Filter - Theme - Light - Dark - + Kotatsu + Local storage + Favourites + History + An error occurred + Network error + Details + Chapters + List + Detailed list + Grid + List mode + Settings + Manga sources + Loading… + Computing… + Chapter %1$d of %2$d + Close + Try again + Clear history + Nothing found + No history yet + Read + No favourites yet + Favourite this + New category + Add + Save + Share + Create shortcut… + Share %s + Search + Search manga + Downloading… + Processing… + Downloaded + Downloads + Name + Popular + Updated + Newest + Rating + Sorting order + Filter + Theme + Light + Dark + Follow system - Pages - Clear - Remove - \"%s\" deleted from local storage - Save page - Saved - Share image - Import - Delete - This operation is not supported - Either pick a ZIP or CBZ file. - No description - Clear page cache - B|kB|MB|GB|TB - Standard - Webtoon - Read mode - Grid size - Search on %s - Delete manga - Permanently delete \"%s\" from device? - Reader settings - Switch pages - Continue - Error - Clear thumbnails cache - Clear search history - Cleared - Internal storage - External storage - Domain - A new version of the app is available - Open in web browser - Notifications - %1$d of %2$d on - New chapters - Download - Notifications settings - Notification sound - LED indicator - Vibration - Favourite categories - Remove - It\'s kind of empty here… - Try to reformulate the query. - What you read will be displayed here - Find what to read in the «Explore» section - Save something first - Save something from an online catalog or import it from a file. - Shelf - Recent - Page animation - Downloads folder - Not available - No available storage - Other storage - Done - All favourites - Empty category - Read later - Updates - New chapters of what you are reading are shown here - Search results - New version: %s - Size: %s - Clear updates feed - Cleared - Rotate screen - Update - Feed update will start soon - Look for updates - Don\'t check - Enter password - Wrong password - Protect the app - Ask for password when starting Kotatsu - Repeat the password - Mismatching passwords - About - Version %s - Check for updates - No updates available - Right-to-left - New category - Scale mode - Fit center - Fit to height - Fit to width - Keep at start - Black - Uses less power on AMOLED screens - Backup and restore - Create data backup - Restore from backup - Restored - Preparing… - File not found - All data was restored - The data was restored, but there are errors - You can create backup of your history and favourites and restore it - Just now - Yesterday - Long ago - Group - Today - Tap to try again - The chosen configuration will be remembered for this manga - Silent - CAPTCHA required - Solve - Clear cookies - All cookies were removed - Clear feed - Clear all update history permanently? - Check for new chapters - Reverse - Grid view - Sign in - Sign in to view this content - Default: %s - Next - Enter a password to start the app with - Confirm - The password must be 4 characters or more - Remove all recent search queries permanently? - Welcome - Backup saved - Some devices have different system behavior, which may break background tasks. - Read more - Queued - The chapter is missing - Translate this app - Translation - Authorized - Logging in on %s is not supported - You will be logged out from all sources - Genres - Finished - Ongoing - Default - Exclude NSFW manga from history - Numbered pages - Screenshot policy - Allow - Block on NSFW - Always block - Suggestions - Enable suggestions - Suggest manga based on your preferences - All data is only analyzed locally on this device and never sent anywhere. - Start reading manga and you will get personalized suggestions - Do not suggest NSFW manga - Enabled - Disabled - Reset filter - Select languages which you want to read manga. You can change it later in settings. - Never - Only on Wi-Fi - Always - Preload pages - Logged in as %s - 18+ - Various languages - Find chapter - No chapters in this manga - %1$s%% - Appearance - Suggestions updating - Exclude genres - Specify genres that you do not want to see in the suggestions - Delete selected items from device permanently? - Removal completed - Shikimori - AniList - Download slowdown - Helps avoid blocking your IP address - Saved manga processing - Chapters will be removed in the background - Canceled - Account already exists - Back - Synchronization - Sync your data - Enter your email to continue - Hide - New manga sources are available - Check for new chapters and notify about it - You will receive notifications about updates of manga you are reading - You will not receive notifications but new chapters will be highlighted in the lists - Enable notifications - Name - Edit - Edit category - Tracking - No favourite categories - Log out - Add bookmark - Remove bookmark - Bookmarks - Bookmark removed - Bookmark added - Undo - Removed from history - DNS over HTTPS - Default mode - Autodetect reader mode - Automatically detect if manga is webtoon - Disable battery optimization - Helps with background updates checks - Something went wrong. Please submit a bug report to the developers to help us fix it. - Send - Planned - Reading - Re-reading - Completed - On hold - Dropped - Disable all - Use fingerprint if available - Manga from your favourites - Your recently read manga - Report - Show reading progress indicators - Data deletion - Show percentage read in history and favourites - Manga marked as NSFW will never be added to the history and your progress will not be saved - Can help in case of some issues. All authorizations will be invalidated - Show all - Invalid domain - Select range - Clear all history - Last 2 hours - History cleared - Manage - No bookmarks yet - You can create bookmark while reading manga - Bookmarks removed - No manga sources - Enable manga sources to read manga online - Random - Are you sure you want to delete the selected favorite categories?\nAll manga in it will be lost and this cannot be undone. - Reorder - Empty - Explore - Press "Back" again to exit - Press "Back" twice to exit the app - Exit confirmation - Saved manga - Pages cache - Other cache - Storage usage - Available - %s - %s - Removed from favourites - Options - Content not found or removed - %1$s · %2$s - Incognito mode - No chapters - Automatic scroll - Ch. %1$d/%2$d Pg. %3$d/%4$d - Show information bar in reader - Comics archive - Folder with images - Importing manga - Import completed - You can delete the original file from storage to save space - Import will start soon - Feed - Error details:<br><tt>%1$s</tt><br><br>1. Try to <a href="%2$s">open manga in a web browser</a> to ensure it is available on its source<br>2. Make sure you are using the <a href="kotatsu://about">latest version of Kotatsu</a><br>3. If it is available, send an error report to the developers. - Show recent manga shortcuts - Make recent manga available by long pressing on application icon - Tapping on the right edge, or pressing the right key, always switches to the next page. - Ergonomic reader control - Color correction - Brightness - Contrast - Reset - Save or discard unsaved changes\? - Discard - No space left on device - Show page switching slider - Webtoon zoom - Network is not available - Turn on Wi-Fi or mobile network to read manga online - Server side error (%1$d). Please try again later - Also clear information about new chapters - Compact - MyAnimeList - Source disabled - Content preloading - Mark as current - Language - Share logs - Enable logging - Record some actions for debug purposes. Don\'t turn it on if you\'re not sure what you\'re doing - Show suspicious content - Dynamic - Color scheme - Show in grid view - Miku - Asuka - Mion - Rikka - Sakura - Mamimi - Kanade - There is nothing here - To track reading progress, select Menu → Track on the manga details screen. - Services - Allow unstable updates - Receive notifications about unstable builds - Download started - Got it - Tap and hold on an item to reorder them - UserAgent header - Please restart the application to apply these changes - You can select one or more .cbz or .zip files, each file will be recognized as a separate manga. - You can select a directory with archives or images. Each archive (or subdirectory) will be recognized as a chapter. - Speed - Show on the Shelf - You can sign in into an existing account or create a new one - Find similar - Synchronization settings - Server address - You can use a self-hosted synchronization server or a default one. Don\'t change this if you\'re not sure what you\'re doing. - Ignore SSL errors - Choose mirror automatically - Automatically switch domains for manga sources on errors if mirrors are available - Pause - Resume - Paused - + Pages + Clear + Remove + \"%s\" deleted from local storage + Save page + Saved + Share image + Import + Delete + This operation is not supported + Either pick a ZIP or CBZ file. + No description + Clear page cache + B|kB|MB|GB|TB + Standard + Webtoon + Read mode + Grid size + Search on %s + Delete manga + Permanently delete \"%s\" from device? + Reader settings + Switch pages + Continue + Error + Clear thumbnails cache + Clear search history + Cleared + Internal storage + External storage + Domain + A new version of the app is available + Open in web browser + Notifications + %1$d of %2$d on + New chapters + Download + Notifications settings + Notification sound + LED indicator + Vibration + Favourite categories + Remove + It\'s kind of empty here… + Try to reformulate the query. + What you read will be displayed here + Find what to read in the «Explore» section + Save something first + Save something from an online catalog or import it from a file. + Shelf + Recent + Page animation + Downloads folder + Not available + No available storage + Other storage + Done + All favourites + Empty category + Read later + Updates + New chapters of what you are reading are shown here + Search results + New version: %s + Size: %s + Clear updates feed + Cleared + Rotate screen + Update + Feed update will start soon + Look for updates + Don\'t check + Enter password + Wrong password + Protect the app + Ask for password when starting Kotatsu + Repeat the password + Mismatching passwords + About + Version %s + Check for updates + No updates available + Right-to-left + New category + Scale mode + Fit center + Fit to height + Fit to width + Keep at start + Black + Uses less power on AMOLED screens + Backup and restore + Create data backup + Restore from backup + Restored + Preparing… + File not found + All data was restored + The data was restored, but there are errors + You can create backup of your history and favourites and restore it + Just now + Yesterday + Long ago + Group + Today + Tap to try again + The chosen configuration will be remembered for this manga + Silent + CAPTCHA required + Solve + Clear cookies + All cookies were removed + Clear feed + Clear all update history permanently? + Check for new chapters + Reverse + Grid view + Sign in + Sign in to view this content + Default: %s + Next + Enter a password to start the app with + Confirm + The password must be 4 characters or more + Remove all recent search queries permanently? + Welcome + Backup saved + Some devices have different system behavior, which may break background tasks. + Read more + Queued + The chapter is missing + Translate this app + Translation + Authorized + Logging in on %s is not supported + You will be logged out from all sources + Genres + Finished + Ongoing + Default + Exclude NSFW manga from history + Numbered pages + Screenshot policy + Allow + Block on NSFW + Always block + Suggestions + Enable suggestions + Suggest manga based on your preferences + All data is only analyzed locally on this device and never sent anywhere. + Start reading manga and you will get personalized suggestions + Do not suggest NSFW manga + Enabled + Disabled + Reset filter + Select languages which you want to read manga. You can change it later in settings. + Never + Only on Wi-Fi + Always + Preload pages + Logged in as %s + 18+ + Various languages + Find chapter + No chapters in this manga + %1$s%% + Appearance + Suggestions updating + Exclude genres + Specify genres that you do not want to see in the suggestions + Delete selected items from device permanently? + Removal completed + Shikimori + AniList + Download slowdown + Helps avoid blocking your IP address + Saved manga processing + Chapters will be removed in the background + Canceled + Account already exists + Back + Synchronization + Sync your data + Enter your email to continue + Hide + New manga sources are available + Check for new chapters and notify about it + You will receive notifications about updates of manga you are reading + You will not receive notifications but new chapters will be highlighted in the lists + Enable notifications + Name + Edit + Edit category + Tracking + No favourite categories + Log out + Add bookmark + Remove bookmark + Bookmarks + Bookmark removed + Bookmark added + Undo + Removed from history + DNS over HTTPS + Default mode + Autodetect reader mode + Automatically detect if manga is webtoon + Disable battery optimization + Helps with background updates checks + Something went wrong. Please submit a bug report to the developers to help us fix it. + Send + Planned + Reading + Re-reading + Completed + On hold + Dropped + Disable all + Use fingerprint if available + Manga from your favourites + Your recently read manga + Report + Show reading progress indicators + Data deletion + Show percentage read in history and favourites + Manga marked as NSFW will never be added to the history and your progress will not be saved + Can help in case of some issues. All authorizations will be invalidated + Show all + Invalid domain + Select range + Clear all history + Last 2 hours + History cleared + Manage + No bookmarks yet + You can create bookmark while reading manga + Bookmarks removed + No manga sources + Enable manga sources to read manga online + Random + Are you sure you want to delete the selected favorite categories?\nAll manga in it will be lost and this cannot be undone. + Reorder + Empty + Explore + Press "Back" again to exit + Press "Back" twice to exit the app + Exit confirmation + Saved manga + Pages cache + Other cache + Storage usage + Available + %1$s - %2$s + Removed from favourites + Options + Content not found or removed + %1$s · %2$s + Incognito mode + No chapters + Automatic scroll + Ch. %1$d/%2$d Pg. %3$d/%4$d + Show information bar in reader + Comics archive + Folder with images + Importing manga + Import completed + You can delete the original file from storage to save space + Import will start soon + Feed + Error details:<br><tt>%1$s</tt><br><br>1. Try to <a href="%2$s">open manga in a web browser</a> to ensure it is available on its source<br>2. Make sure you are using the <a href="kotatsu://about">latest version of Kotatsu</a><br>3. If it is available, send an error report to the developers. + Show recent manga shortcuts + Make recent manga available by long pressing on application icon + Tapping on the right edge, or pressing the right key, always switches to the next page. + Ergonomic reader control + Color correction + Brightness + Contrast + Reset + Save or discard unsaved changes\? + Discard + No space left on device + Show page switching slider + Webtoon zoom + Network is not available + Turn on Wi-Fi or mobile network to read manga online + Server side error (%1$d). Please try again later + Also clear information about new chapters + Compact + MyAnimeList + Source disabled + Content preloading + Mark as current + Language + Share logs + Enable logging + Record some actions for debug purposes. Don\'t turn it on if you\'re not sure what you\'re doing + Show suspicious content + Dynamic + Color scheme + Show in grid view + Miku + Asuka + Mion + Rikka + Sakura + Mamimi + Kanade + There is nothing here + To track reading progress, select Menu → Track on the manga details screen. + Services + Allow unstable updates + Receive notifications about unstable builds + Download started + Got it + Tap and hold on an item to reorder them + UserAgent header + Please restart the application to apply these changes + You can select one or more .cbz or .zip files, each file will be recognized as a separate manga. + You can select a directory with archives or images. Each archive (or subdirectory) will be recognized as a chapter. + Speed + Show on the Shelf + You can sign in into an existing account or create a new one + Find similar + Synchronization settings + Server address + You can use a self-hosted synchronization server or a default one. Don\'t change this if you\'re not sure what you\'re doing. + Ignore SSL errors + Choose mirror automatically + Automatically switch domains for manga sources on errors if mirrors are available + Pause + Resume + Paused + Remove completed - Cancel all - Download only via Wi-Fi - Stop downloading when switching to a mobile network - Suggestion: %s - Sometimes show notifications with suggested manga - More - Enable - No thanks - All active downloads will be cancelled, partially downloaded data will be lost - Your downloads history will be permanently deleted - You don\'t have any downloads - Downloads have been resumed - Downloads have been paused - Downloads have been removed - Downloads have been cancelled - Do you want to receive personalized manga suggestions? - WebView not available: check if WebView provider is installed - Clear network cache - Type - Address - Port - Proxy - Invalid value - Kitsu - Enter your email and password to continue - Downloaded - Images optimization proxy - Use the wsrv.nl service to reduce traffic usage and speed up image loading if possible - Invert colors - Username - Password - Authorization (optional) - Invalid port number - Network - Data and privacy - Restore previously created backup - Allow zoom in gesture in webtoon mode - Show the current time and reading progress at the top of the screen - Show page numbers in bottom corner - Clear cookies for specified domain only. In most cases will invalidate authorization - All chapters with translation %s - The whole manga - First %s - Next unread %s - All unread chapters - All unread chapters (%s) - Select chapters manually - Pick custom directory - You have no access to this file or directory - Local manga directories - Description - This month - Voice search - Related manga - Light - Dark - White - Black - Background - Data was not restored - Make sure you have selected the correct backup file - Manage categories - Do not update suggestions using metered network connections - Do not check for new chapters using metered network connections - Enter manga title, genre or source name - Progress - Added - Show - %s requires a captcha to be resolved to work properly - Languages - Unknown - In progress - Disable NSFW - Too many requests. Try again later - Show a list of related manga. In some cases it may be inaccurate or missing - Advanced - Manga list - Invalid data is returned or file is corrupted - On device - Directories - Main screen sections - No more items can be added - To top - Moved to top - Zoom out - Zoom in - Show zoom buttons - Whether to show zoom control buttons in the bottom right corner - Keep screen on - Do not turn the screen off while you\'re reading manga - Dropped - Reduces banding, but may impact performance - 32-bit color mode - Suggest new sources after app update - Prompt to enable newly added sources after updating the application - List options - Relevance - Categories - Online variant - Periodic backups - Backup creation frequency - Every day - Every 2 days - Once per week - Twice per month - Once per month - Enable periodic backups - Backups output directory - Last successful backup: %s - x%.1f - Lock screen rotation - Manga - Hentai - Comics - Other - %1$s, %2$s - Sources catalog - Source enabled - There are no sources available in this section, or all of it might have been already added.\nStay tuned - No available manga sources found by your query - Catalog - Manage sources - Manual - Available: %1$d - Disable NSFW sources and hide adult manga from list if possible - Paused - Reduce memory consumption (beta) - Reduce offscreen pages quality to use less memory - State - Filtering by multiple genres is not supported by this manga source - Filtering by multiple states is not supported by this manga source - Search is not supported by this manga source - You can enable download slowdown for each manga source individually in the source settings if you are having problems with server-side blocking - Skip - Grayscale - Globally - This manga - These settings can be applied globally or only to the current manga. If applied globally, individual settings will not be overridden. - Apply - Filtering by both genres and locale is not supported by this source - Filtering by both genres and states is not supported by this source - Start typing the genre name - Might help with getting the download started if you have any issues with it - Please select which content sources you would like to enable. This can also be configured later in settings - Login to sync account - Restore - Backup date: %s - Upcoming - Name reversed - Content rating - Exclude genres - Safe - Suggestive - Adult - Default tab - Mark as completed - Mark selected manga as completely read?\n\nWarning: current reading progress will be lost. - This category was hidden from the main screen and is accessible through Menu → Manage categories - %1$s %2$s - Volume %d - Unknown volume - Your reading progress will not be saved - Vertical - Last read - Show menu - Show/hide UI - Previous chapter - Next chapter - Previous page - Next page - Reader actions - Configure actions for tappable screen areas - Enable volume buttons - Use volume buttons for switching pages - Tap action - Long tap action - None - Reset settings to default values? This action cannot be undone. - Use two pages layout on landscape orientation (beta) - Default webtoon zoom out - Fullscreen mode - Hide system status and navigation bars - %1$d/%2$d - Show estimated reading time - The time estimation value may be inaccurate - Suggestions feature is disabled - Checking for new chapters is disabled - Show labels in navigation bar - Saving pages - Ask for the destination dir every time - Default page save directory - Remove from history - Location - Preferred download format - Automatic - Single CBZ file - Multiple CBZ files - Reading statistics - Other manga - Less than a minute - Statistics - Clear statistics - Statistics cleared - Do you really want to clear all reading statistics? This action cannot be undone. - Week - Month - All time - Day - Three months - There are no statistics for the selected period - Pages read: %s - Alternatives - Migrate - Manga \"%1$s\" from \"%2$s\" will be replaced with \"%3$s\" from \"%4$s\" in your history and favorites (if present) - Manga migration - Migration completed - Delete read chapters - No chapters have been deleted - Removed %1$s, cleared %2$s - Delete chapters you have already read from local storage to free up space - This will permanently delete all chapters marked as read from your local storage. You can re-download it later, but the imported chapters may be lost forever - Delete read chapters automatically - Runs when the application starts - Split by translations - Show chapters with different translations separately, rather than in one list - Oldest - Long time ago read - Unread - Enable source - This manga source is not supported - Show pages thumbnails - Enable the \"Pages\" tab on the details screen - No data was received from server - Please select a proper Kotatsu backup file - (+%d) - + Cancel all + Download only via Wi-Fi + Stop downloading when switching to a mobile network + Suggestion: %s + Sometimes show notifications with suggested manga + More + Enable + No thanks + All active downloads will be cancelled, partially downloaded data will be lost + Your downloads history will be permanently deleted + You don\'t have any downloads + Downloads have been resumed + Downloads have been paused + Downloads have been removed + Downloads have been cancelled + Do you want to receive personalized manga suggestions? + WebView not available: check if WebView provider is installed + Clear network cache + Type + Address + Port + Proxy + Invalid value + Kitsu + Enter your email and password to continue + Downloaded + Images optimization proxy + Use the wsrv.nl service to reduce traffic usage and speed up image loading if possible + Invert colors + Username + Password + Authorization (optional) + Invalid port number + Network + Data and privacy + Restore previously created backup + Allow zoom in gesture in webtoon mode + Show the current time and reading progress at the top of the screen + Show page numbers in bottom corner + Clear cookies for specified domain only. In most cases will invalidate authorization + All chapters with translation %s + The whole manga + First %s + Next unread %s + All unread chapters + All unread chapters (%s) + Select chapters manually + Pick custom directory + You have no access to this file or directory + Local manga directories + Description + This month + Voice search + Related manga + Light + Dark + White + Black + Background + Data was not restored + Make sure you have selected the correct backup file + Manage categories + Do not update suggestions using metered network connections + Do not check for new chapters using metered network connections + Enter manga title, genre or source name + Progress + Added + Show + %s requires a captcha to be resolved to work properly + Languages + Unknown + In progress + Disable NSFW + Too many requests. Try again later + Show a list of related manga. In some cases it may be inaccurate or missing + Advanced + Manga list + Invalid data is returned or file is corrupted + On device + Directories + Main screen sections + No more items can be added + To top + Moved to top + Zoom out + Zoom in + Show zoom buttons + Whether to show zoom control buttons in the bottom right corner + Keep screen on + Do not turn the screen off while you\'re reading manga + Dropped + Reduces banding, but may impact performance + 32-bit color mode + Suggest new sources after app update + Prompt to enable newly added sources after updating the application + List options + Relevance + Categories + Online variant + Periodic backups + Backup creation frequency + Every day + Every 2 days + Once per week + Twice per month + Once per month + Enable periodic backups + Backups output directory + Last successful backup: %s + x%.1f + Lock screen rotation + Manga + Hentai + Comics + Other + %1$s, %2$s + Sources catalog + Source enabled + There are no sources available in this section, or all of it might have been already added.\nStay tuned + No available manga sources found by your query + Catalog + Manage sources + Manual + Available: %1$d + Disable NSFW sources and hide adult manga from list if possible + Paused + Reduce memory consumption (beta) + Reduce offscreen pages quality to use less memory + State + Filtering by multiple genres is not supported by this manga source + Filtering by multiple states is not supported by this manga source + Search is not supported by this manga source + You can enable download slowdown for each manga source individually in the source settings if you are having problems with server-side blocking + Skip + Grayscale + Globally + This manga + These settings can be applied globally or only to the current manga. If applied globally, individual settings will not be overridden. + Apply + Filtering by both genres and locale is not supported by this source + Filtering by both genres and states is not supported by this source + Start typing the genre name + Might help with getting the download started if you have any issues with it + Please select which content sources you would like to enable. This can also be configured later in settings + Login to sync account + Restore + Backup date: %s + Upcoming + Name reversed + Content rating + Exclude genres + Safe + Suggestive + Adult + Default tab + Mark as completed + Mark selected manga as completely read?\n\nWarning: current reading progress will be lost. + This category was hidden from the main screen and is accessible through Menu → Manage categories + %1$s %2$s + Volume %d + Unknown volume + Your reading progress will not be saved + Vertical + Last read + Show menu + Show/hide UI + Previous chapter + Next chapter + Previous page + Next page + Reader actions + Configure actions for tappable screen areas + Enable volume buttons + Use volume buttons for switching pages + Tap action + Long tap action + None + Reset settings to default values? This action cannot be undone. + Use two pages layout on landscape orientation (beta) + Default webtoon zoom out + Fullscreen mode + Hide system status and navigation bars + %1$d/%2$d + Show estimated reading time + The time estimation value may be inaccurate + Suggestions feature is disabled + Checking for new chapters is disabled + Show labels in navigation bar + Saving pages + Ask for the destination dir every time + Default page save directory + Remove from history + Location + Preferred download format + Automatic + Single CBZ file + Multiple CBZ files + Reading statistics + Other manga + Less than a minute + Statistics + Clear statistics + Statistics cleared + Do you really want to clear all reading statistics? This action cannot be undone. + Week + Month + All time + Day + Three months + There are no statistics for the selected period + Pages read: %s + Alternatives + Migrate + Manga \"%1$s\" from \"%2$s\" will be replaced with \"%3$s\" from \"%4$s\" in your history and favorites (if present) + Manga migration + Migration completed + Delete read chapters + No chapters have been deleted + Removed %1$s, cleared %2$s + Delete chapters you have already read from local storage to free up space + This will permanently delete all chapters marked as read from your local storage. You can re-download it later, but the imported chapters may be lost forever + Delete read chapters automatically + Runs when the application starts + Split by translations + Show chapters with different translations separately, rather than in one list + Oldest + Long time ago read + Unread + Enable source + This manga source is not supported + Show pages thumbnails + Enable the \"Pages\" tab on the details screen + No data was received from server + Please select a proper Kotatsu backup file + (+%d) + %d h - + %d m - + %1$d h %2$d m - Fix - There is no permission to access manga on external storage - Last used - Show updated - Gaps in webtoon mode - Show vertical gaps between pages in webtoon mode - Less frequently - More frequently - Frequency of check - %1$s: %2$d - Pin navigation UI - Do not hide navigation bar and search view on scroll + Fix + There is no permission to access manga on external storage + Last used + Show updated + Gaps in webtoon mode + Show vertical gaps between pages in webtoon mode + Less frequently + More frequently + Frequency of check + %1$s: %2$d + Pin navigation UI + Do not hide navigation bar and search view on scroll Search suggestions Recent queries Suggested queries diff --git a/build.gradle b/build.gradle index 56f9c2023..c71dac215 100644 --- a/build.gradle +++ b/build.gradle @@ -5,9 +5,9 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.4.0' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24' classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1' - classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.23-1.0.20' + classpath 'com.google.devtools.ksp:symbol-processing-gradle-plugin:1.9.24-1.0.20' } }