Compare commits

..

14 Commits

Author SHA1 Message Date
627a00beb4 Update parsers 2022-10-01 14:00:11 +03:00
e00ed13ad1 Fix badge alignment on details screen 2022-10-01 13:06:00 +03:00
893fa6bd90 Fix opening fingerprint dialog 2022-10-01 12:19:24 +03:00
512188c8dd Option to disable reader slider #228 2022-10-01 11:42:28 +03:00
aae6761809 Remove unused code 2022-10-01 10:39:58 +03:00
c3f055d0c4 Fix widgets colors 2022-10-01 10:39:58 +03:00
Tony
04d5df20d1 Translated using Weblate (Portuguese)
Currently translated at 69.0% (268 of 388 strings)

Translated using Weblate (Portuguese)

Currently translated at 68.8% (267 of 388 strings)

Co-authored-by: Tony <t.tony.br01@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/pt/
Translation: Kotatsu/Strings
2022-10-01 10:23:12 +03:00
Eric
665eca0699 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (8 of 8 strings)

Co-authored-by: Eric <hamburger1024@mailbox.org>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/plurals/zh_Hans/
Translation: Kotatsu/plurals
2022-10-01 10:23:12 +03:00
Faris Daffa
9a1534464f Translated using Weblate (Indonesian)
Currently translated at 93.2% (362 of 388 strings)

Co-authored-by: Faris Daffa <faris.6dsdiaf@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/id/
Translation: Kotatsu/Strings
2022-10-01 10:23:12 +03:00
kuragehime
f856fc6fac Translated using Weblate (Chinese (Traditional))
Currently translated at 27.5% (107 of 388 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 26.2% (102 of 388 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (388 of 388 strings)

Added translation using Weblate (Chinese (Traditional))

Co-authored-by: kuragehime <kuragehime641@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/ja/
Translate-URL: https://hosted.weblate.org/projects/kotatsu/strings/zh_Hant/
Translation: Kotatsu/Strings
2022-10-01 10:23:12 +03:00
4af8e73303 Fix crashes in CoroutineIntentService 2022-10-01 09:28:11 +03:00
23239f1fec Add setReorderingAllowed for fragment transactions 2022-09-23 18:30:38 +03:00
853e4d6fde Follow guidelines for some dialogs 2022-09-22 20:00:43 +03:00
14a37ad16e Minor adjustments 2022-09-22 20:00:34 +03:00
44 changed files with 435 additions and 218 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 33
versionCode 498
versionName '4.0-beta2'
versionCode 499
versionName '4.0-rc1'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -60,6 +60,7 @@ android {
'-opt-in=kotlinx.coroutines.FlowPreview',
'-opt-in=kotlin.contracts.ExperimentalContracts',
'-opt-in=coil.annotation.ExperimentalCoilApi',
'-opt-in=com.google.android.material.badge.ExperimentalBadgeUtils',
]
}
lint {
@@ -82,15 +83,15 @@ afterEvaluate {
}
}
dependencies {
implementation('com.github.KotatsuApp:kotatsu-parsers:b3a9c5fcda') {
implementation('com.github.KotatsuApp:kotatsu-parsers:b1990c7918') {
exclude group: 'org.json', module: 'json'
}
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.activity:activity-ktx:1.5.1'
implementation 'androidx.fragment:fragment-ktx:1.5.2'
implementation 'androidx.activity:activity-ktx:1.6.0'
implementation 'androidx.fragment:fragment-ktx:1.5.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
implementation 'androidx.lifecycle:lifecycle-service:2.5.1'
@@ -101,7 +102,7 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha05'
implementation 'com.google.android.material:material:1.7.0-rc01'
//noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.1'
@@ -117,8 +118,8 @@ dependencies {
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl:4.3.2'
implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2'
implementation "com.google.dagger:hilt-android:2.43.2"
kapt "com.google.dagger:hilt-compiler:2.43.2"
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
@@ -146,6 +147,6 @@ dependencies {
androidTestImplementation 'androidx.room:room-testing:2.4.3'
androidTestImplementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.43.2'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.43.2'
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.44'
}

View File

@@ -4,11 +4,13 @@ import android.app.Service
import android.content.Intent
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
abstract class CoroutineIntentService : BaseService() {
@@ -21,11 +23,13 @@ abstract class CoroutineIntentService : BaseService() {
return Service.START_REDELIVER_INTENT
}
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch {
private fun launchCoroutine(intent: Intent?, startId: Int) = lifecycleScope.launch(errorHandler(startId)) {
mutex.withLock {
try {
withContext(dispatcher) {
processIntent(intent)
if (intent != null) {
withContext(dispatcher) {
processIntent(startId, intent)
}
}
} finally {
stopSelf(startId)
@@ -33,5 +37,12 @@ abstract class CoroutineIntentService : BaseService() {
}
}
protected abstract suspend fun processIntent(intent: Intent?)
protected abstract suspend fun processIntent(startId: Int, intent: Intent)
protected abstract fun onError(startId: Int, error: Throwable)
private fun errorHandler(startId: Int) = CoroutineExceptionHandler { _, throwable ->
throwable.printStackTraceDebug()
onError(startId, throwable)
}
}

View File

@@ -11,12 +11,6 @@ import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.network.DoHProvider
@@ -26,6 +20,14 @@ import org.koitharu.kotatsu.utils.ext.getEnumValue
import org.koitharu.kotatsu.utils.ext.observe
import org.koitharu.kotatsu.utils.ext.putEnumValue
import org.koitharu.kotatsu.utils.ext.toUriOrNull
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Collections
import java.util.EnumSet
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AppSettings @Inject constructor(@ApplicationContext context: Context) {
@@ -206,6 +208,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isReaderBarEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_BAR, true)
val isReaderSliderEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_SLIDER, true)
val dnsOverHttps: DoHProvider
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
@@ -328,6 +333,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_INCOGNITO_MODE = "incognito"
const val KEY_SYNC = "sync"
const val KEY_READER_BAR = "reader_bar"
const val KEY_READER_SLIDER = "reader_slider"
const val KEY_SHORTCUTS = "dynamic_shortcuts"
const val KEY_READER_TAPS_LTR = "reader_taps_ltr"
const val KEY_LOCAL_LIST_ORDER = "local_order"

View File

@@ -54,7 +54,7 @@ class MangaErrorDialog : AlertDialogFragment<DialogMangaErrorBinding>() {
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.report) { _, _ ->
dismiss()
error.report(TAG)
error.report()
}.setTitle(R.string.error_occurred)
}

View File

@@ -19,12 +19,10 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.BaseTransientBottomBar
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent
@@ -37,12 +35,17 @@ import org.koitharu.kotatsu.core.ui.MangaErrorDialog
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.list.ui.adapter.bindBadge
import org.koitharu.kotatsu.main.ui.owners.NoModalBottomSheetOwner
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.reader.ui.ReaderActivity
import org.koitharu.kotatsu.reader.ui.ReaderState
import org.koitharu.kotatsu.utils.ext.*
import org.koitharu.kotatsu.utils.ViewBadge
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.isReportable
import org.koitharu.kotatsu.utils.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.utils.ext.textAndVisible
import javax.inject.Inject
@AndroidEntryPoint
class DetailsActivity :
@@ -60,7 +63,7 @@ class DetailsActivity :
@Inject
lateinit var shortcutsUpdater: ShortcutsUpdater
private var badge: BadgeDrawable? = null
private lateinit var viewBadge: ViewBadge
private val viewModel: DetailsViewModel by assistedViewModels {
viewModelFactory.create(MangaIntent(intent))
@@ -83,6 +86,7 @@ class DetailsActivity :
}
binding.buttonRead.setOnClickListener(this)
binding.buttonDropdown.setOnClickListener(this)
viewBadge = ViewBadge(binding.buttonRead, this)
chaptersMenuProvider = if (binding.layoutBottom != null) {
val bsMediator = ChaptersBottomSheetMediator(checkNotNull(binding.layoutBottom))
@@ -151,6 +155,7 @@ class DetailsActivity :
)
}
}
R.id.button_dropdown -> showBranchPopupMenu()
}
}
@@ -188,10 +193,12 @@ class DetailsActivity :
ExceptionResolver.canResolve(e) -> {
resolveError(e)
}
manga == null -> {
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
finishAfterTransition()
}
else -> {
val snackbar = makeSnackbar(
e.getDisplayMessage(resources),
@@ -242,7 +249,7 @@ class DetailsActivity :
}
private fun onNewChaptersChanged(newChapters: Int) {
badge = binding.buttonRead.bindBadge(badge, newChapters)
viewBadge.counter = newChapters
}
fun showChapterMissingDialog(chapterId: Long) {

View File

@@ -34,7 +34,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>() {
setContentView(ActivityDownloadsBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val adapter = DownloadsAdapter(lifecycleScope, coil)
val spacing = resources.getDimensionPixelOffset(R.dimen.grid_spacing)
val spacing = resources.getDimensionPixelOffset(R.dimen.list_spacing)
binding.recyclerView.addItemDecoration(SpacingItemDecoration(spacing))
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.adapter = adapter

View File

@@ -16,7 +16,12 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.transformWhile
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
@@ -85,7 +90,9 @@ class DownloadService : BaseService() {
override fun onDestroy() {
unregisterReceiver(controlReceiver)
wakeLock.release()
if (wakeLock.isHeld) {
wakeLock.release()
}
isRunning = false
super.onDestroy()
}
@@ -169,6 +176,7 @@ class DownloadService : BaseService() {
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
jobs[cancelId]?.cancel()
}
ACTION_DOWNLOAD_RESUME -> {
val cancelId = intent.getIntExtra(EXTRA_CANCEL_ID, 0)
jobs[cancelId]?.resume()

View File

@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.core.model.FavouriteCategory
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
import kotlin.text.Typography.dagger
@AndroidEntryPoint
class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
@@ -28,6 +29,7 @@ class FavouritesActivity : BaseActivity<ActivityContainerBinding>() {
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
setReorderingAllowed(true)
val fragment = FavouritesListFragment.newInstance(intent.getLongExtra(EXTRA_CATEGORY_ID, NO_ID))
replace(R.id.container, fragment)
}

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import kotlin.text.Typography.dagger
@AndroidEntryPoint
class HistoryActivity :
@@ -28,6 +29,7 @@ class HistoryActivity :
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
setReorderingAllowed(true)
val fragment = HistoryListFragment.newInstance()
replace(R.id.container, fragment)
}

View File

@@ -1,8 +1,5 @@
@file:SuppressLint("UnsafeOptInUsageError")
package org.koitharu.kotatsu.list.ui.adapter
import android.annotation.SuppressLint
import android.view.View
import androidx.annotation.CheckResult
import androidx.core.view.doOnNextLayout
@@ -42,4 +39,4 @@ private fun initBadge(anchor: View): BadgeDrawable {
private fun BadgeDrawable.align() {
horizontalOffset = intrinsicWidth
verticalOffset = intrinsicHeight
}
}

View File

@@ -23,7 +23,12 @@ import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.local.domain.importer.MangaImporter
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.PendingIntentCompat
import org.koitharu.kotatsu.utils.ext.*
import org.koitharu.kotatsu.utils.ext.asArrayList
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.report
import org.koitharu.kotatsu.utils.ext.toBitmapOrNull
import javax.inject.Inject
@AndroidEntryPoint
@@ -48,8 +53,8 @@ class ImportService : CoroutineIntentService() {
super.onDestroy()
}
override suspend fun processIntent(intent: Intent?) {
val uris = intent?.getParcelableArrayListExtra<Uri>(EXTRA_URIS)
override suspend fun processIntent(startId: Int, intent: Intent) {
val uris = intent.getParcelableArrayListExtra<Uri>(EXTRA_URIS)
if (uris.isNullOrEmpty()) {
return
}
@@ -69,6 +74,10 @@ class ImportService : CoroutineIntentService() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
override fun onError(startId: Int, error: Throwable) {
error.report()
}
private suspend fun importImpl(uri: Uri): Manga {
val importer = importerFactory.create(uri)
return importer.import(uri)

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import javax.inject.Inject
@@ -34,8 +35,8 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
super.onDestroy()
}
override suspend fun processIntent(intent: Intent?) {
val manga = intent?.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
override suspend fun processIntent(startId: Int, intent: Intent) {
val manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
startForeground()
val mangaWithChapters = localMangaRepository.getDetails(manga)
@@ -47,6 +48,21 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
}
override fun onError(startId: Int, error: Throwable) {
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(getString(R.string.error_occurred))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setDefaults(0)
.setColor(ContextCompat.getColor(this, R.color.blue_primary_dark))
.setSilent(true)
.setContentText(error.getDisplayMessage(resources))
.setSmallIcon(android.R.drawable.stat_notify_error)
.setAutoCancel(true)
.build()
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(NOTIFICATION_ID + startId, notification)
}
private fun startForeground() {
val title = getString(R.string.local_manga_processing)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

View File

@@ -52,6 +52,7 @@ import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
import org.koitharu.kotatsu.tracker.work.TrackWorker
import org.koitharu.kotatsu.utils.VoiceInputContract
import org.koitharu.kotatsu.utils.ext.*
import kotlin.text.Typography.dagger
private const val TAG_SEARCH = "search"
@@ -127,10 +128,12 @@ class MainActivity :
binding.searchView.clearFocus()
when {
fragment != null -> supportFragmentManager.commit {
setReorderingAllowed(true)
remove(fragment)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
runOnCommit { onSearchClosed() }
}
else -> super.onBackPressed()
}
}
@@ -173,6 +176,7 @@ class MainActivity :
if (v?.id == R.id.searchView && hasFocus) {
if (fragment == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.container, SearchSuggestionFragment.newInstance(), TAG_SEARCH)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
runOnCommit { onSearchOpened() }

View File

@@ -85,6 +85,7 @@ class MainNavigationDelegate(
private fun setPrimaryFragment(fragment: Fragment) {
fragmentManager.beginTransaction()
.setReorderingAllowed(true)
.replace(R.id.container, fragment, TAG_PRIMARY)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit()

View File

@@ -49,7 +49,10 @@ class ProtectActivity :
startActivity(intent)
finishAfterTransition()
}
}
override fun onStart() {
super.onStart()
if (!useFingerprint()) {
binding.editPassword.requestFocus()
}

View File

@@ -9,15 +9,23 @@ import android.transition.Fade
import android.transition.Slide
import android.transition.TransitionManager
import android.transition.TransitionSet
import android.view.*
import android.view.Gravity
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import androidx.core.graphics.Insets
import androidx.core.view.*
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -42,7 +50,16 @@ import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.utils.GridTouchHelper
import org.koitharu.kotatsu.utils.IdlingDetector
import org.koitharu.kotatsu.utils.ShareHelper
import org.koitharu.kotatsu.utils.ext.*
import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.utils.ext.hasGlobalPoint
import org.koitharu.kotatsu.utils.ext.isReportable
import org.koitharu.kotatsu.utils.ext.observeWithPrevious
import org.koitharu.kotatsu.utils.ext.postDelayed
import org.koitharu.kotatsu.utils.ext.report
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@AndroidEntryPoint
class ReaderActivity :
@@ -145,6 +162,7 @@ class ReaderActivity :
R.id.action_settings -> {
startActivity(SettingsActivity.newReaderSettingsIntent(this))
}
R.id.action_chapters -> {
ChaptersBottomSheet.show(
supportFragmentManager,
@@ -152,6 +170,7 @@ class ReaderActivity :
viewModel.getCurrentState()?.chapterId ?: 0L,
)
}
R.id.action_pages_thumbs -> {
val pages = viewModel.getCurrentChapterPages()
if (!pages.isNullOrEmpty()) {
@@ -165,6 +184,7 @@ class ReaderActivity :
return false
}
}
R.id.action_bookmark -> {
if (viewModel.isBookmarkAdded.value == true) {
viewModel.removeBookmark()
@@ -172,11 +192,13 @@ class ReaderActivity :
viewModel.addBookmark()
}
}
R.id.action_options -> {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return false
ReaderConfigBottomSheet.show(supportFragmentManager, currentMode)
}
else -> return super.onOptionsItemSelected(item)
}
return true
@@ -364,7 +386,7 @@ class ReaderActivity :
binding.toastView.showTemporary(uiState.chapterName, TOAST_DURATION)
}
}
if (uiState.totalPages > 1 && uiState.currentPage < uiState.totalPages) {
if (uiState.isSliderAvailable()) {
binding.slider.valueTo = uiState.totalPages.toFloat() - 1
binding.slider.value = uiState.currentPage.toFloat()
binding.slider.isVisible = true
@@ -383,7 +405,7 @@ class ReaderActivity :
if (ExceptionResolver.canResolve(exception)) {
tryResolve(exception)
} else {
exception.report("ReaderActivity::onError")
exception.report()
}
} else {
onCancel(dialog)

View File

@@ -1,83 +0,0 @@
package org.koitharu.kotatsu.reader.ui
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.databinding.DialogReaderConfigBinding
import org.koitharu.kotatsu.utils.ext.withArgs
@Deprecated("Not in use")
class ReaderConfigDialog :
AlertDialogFragment<DialogReaderConfigBinding>(),
CheckableButtonGroup.OnCheckedChangeListener {
private lateinit var mode: ReaderMode
override fun onInflateView(
inflater: LayoutInflater,
container: ViewGroup?,
) = DialogReaderConfigBinding.inflate(inflater, container, false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mode = arguments?.getInt(ARG_MODE)
?.let { ReaderMode.valueOf(it) }
?: ReaderMode.STANDARD
}
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
return super.onBuildDialog(builder)
.setTitle(R.string.read_mode)
.setPositiveButton(R.string.done, null)
.setCancelable(true)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonStandard.isChecked = mode == ReaderMode.STANDARD
binding.buttonReversed.isChecked = mode == ReaderMode.REVERSED
binding.buttonWebtoon.isChecked = mode == ReaderMode.WEBTOON
binding.checkableGroup.onCheckedChangeListener = this
}
override fun onDismiss(dialog: DialogInterface) {
(
(parentFragment as? Callback)
?: (activity as? Callback)
)?.onReaderModeChanged(mode)
super.onDismiss(dialog)
}
override fun onCheckedChanged(group: CheckableButtonGroup, checkedId: Int) {
mode = when (checkedId) {
R.id.button_standard -> ReaderMode.STANDARD
R.id.button_webtoon -> ReaderMode.WEBTOON
R.id.button_reversed -> ReaderMode.REVERSED
else -> return
}
}
interface Callback {
fun onReaderModeChanged(mode: ReaderMode)
}
companion object {
private const val TAG = "ReaderConfigDialog"
private const val ARG_MODE = "mode"
fun show(fm: FragmentManager, mode: ReaderMode) = ReaderConfigDialog().withArgs(1) {
putInt(ARG_MODE, mode.id)
}.show(fm, TAG)
}
}

View File

@@ -8,7 +8,7 @@ import org.koitharu.kotatsu.reader.ui.pager.BaseReader
import org.koitharu.kotatsu.reader.ui.pager.reversed.ReversedReaderFragment
import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment
import org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonReaderFragment
import java.util.*
import java.util.EnumMap
class ReaderManager(
private val fragmentManager: FragmentManager,
@@ -35,11 +35,15 @@ class ReaderManager(
fun replace(newMode: ReaderMode) {
val readerClass = requireNotNull(modeMap[newMode])
fragmentManager.commit {
setReorderingAllowed(true)
replace(containerResId, readerClass, null, null)
}
}
fun replace(reader: BaseReader<*>) {
fragmentManager.commit { replace(containerResId, reader) }
fragmentManager.commit {
setReorderingAllowed(true)
replace(containerResId, reader)
}
}
}
}

View File

@@ -10,10 +10,22 @@ import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import java.util.*
import javax.inject.Provider
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.domain.MangaIntent
@@ -21,7 +33,11 @@ import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.*
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.prefs.ScreenshotsPolicy
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.prefs.observeAsLiveData
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.parsers.exception.NotFoundException
@@ -39,6 +55,8 @@ import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.requireValue
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import java.util.Date
import javax.inject.Provider
private const val BOUNDS_PAGE_OFFSET = 2
private const val PREFETCH_LIMIT = 10
@@ -116,6 +134,10 @@ class ReaderViewModel @AssistedInject constructor(
init {
loadImpl()
settings.observe()
.onEach { key ->
if (key == AppSettings.KEY_READER_SLIDER) notifyStateChanged()
}.launchIn(viewModelScope)
}
override fun onCleared() {
@@ -348,6 +370,7 @@ class ReaderViewModel @AssistedInject constructor(
chaptersTotal = chapters.size(),
totalPages = if (chapter != null) chaptersLoader.getPagesCount(chapter.id) else 0,
currentPage = state?.page ?: 0,
isSliderEnabled = settings.isReaderSliderEnabled,
)
uiState.postValue(newState)
}

View File

@@ -7,8 +7,13 @@ data class ReaderUiState(
val chaptersTotal: Int,
val currentPage: Int,
val totalPages: Int,
private val isSliderEnabled: Boolean,
) {
fun isSliderAvailable(): Boolean {
return isSliderEnabled && totalPages > 1 && currentPage < totalPages
}
fun computePercent(): Float {
val ppc = 1f / chaptersTotal
val chapterIndex = chapterNumber - 1

View File

@@ -18,6 +18,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import kotlin.text.Typography.dagger
@AndroidEntryPoint
class MangaListActivity :
@@ -41,6 +42,7 @@ class MangaListActivity :
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
setReorderingAllowed(true)
val fragment = if (source == MangaSource.LOCAL) {
LocalListFragment.newInstance()
} else {

View File

@@ -15,6 +15,7 @@ import org.koitharu.kotatsu.databinding.ActivitySearchBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionViewModel
import org.koitharu.kotatsu.utils.ext.showKeyboard
import kotlin.text.Typography.dagger
@AndroidEntryPoint
class SearchActivity : BaseActivity<ActivitySearchBinding>(), SearchView.OnQueryTextListener {
@@ -61,6 +62,7 @@ class SearchActivity : BaseActivity<ActivitySearchBinding>(), SearchView.OnQuery
}
title = query
supportFragmentManager.commit {
setReorderingAllowed(true)
replace(R.id.container, SearchFragment.newInstance(source, q))
}
binding.searchView.clearFocus()

View File

@@ -91,8 +91,8 @@ class ToolsFragment :
binding.cardUpdate.root.isVisible = false
return
}
binding.cardUpdate.textPrimary.text = getString(R.string.app_update_available_s, version.name)
binding.cardUpdate.textSecondary.text = version.description
binding.cardUpdate.textSecondary.text = getString(R.string.new_version_s, version.name)
binding.cardUpdate.textChangelog.text = version.description
binding.cardUpdate.root.isVisible = true
}

View File

@@ -12,6 +12,7 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityContainerBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import kotlin.text.Typography.dagger
@AndroidEntryPoint
class SuggestionsActivity :
@@ -28,6 +29,7 @@ class SuggestionsActivity :
val fm = supportFragmentManager
if (fm.findFragmentById(R.id.container) == null) {
fm.commit {
setReorderingAllowed(true)
val fragment = SuggestionsFragment.newInstance()
replace(R.id.container, fragment)
}

View File

@@ -0,0 +1,62 @@
package org.koitharu.kotatsu.utils
import android.view.View
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.google.android.material.badge.BadgeDrawable
import com.google.android.material.badge.BadgeUtils
class ViewBadge(
private val anchor: View,
lifecycleOwner: LifecycleOwner,
) : View.OnLayoutChangeListener, DefaultLifecycleObserver {
private var badgeDrawable: BadgeDrawable? = null
var counter: Int
get() = badgeDrawable?.number ?: 0
set(value) {
val badge = badgeDrawable ?: initBadge()
badge.number = value
badge.isVisible = value > 0
}
init {
lifecycleOwner.lifecycle.addObserver(this)
}
override fun onLayoutChange(
v: View?,
left: Int,
top: Int,
right: Int,
bottom: Int,
oldLeft: Int,
oldTop: Int,
oldRight: Int,
oldBottom: Int,
) {
val badge = badgeDrawable ?: return
BadgeUtils.setBadgeDrawableBounds(badge, anchor, null)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
clearBadge()
}
private fun initBadge(): BadgeDrawable {
val badge = BadgeDrawable.create(anchor.context)
anchor.addOnLayoutChangeListener(this)
BadgeUtils.attachBadgeDrawable(badge, anchor)
badgeDrawable = badge
return badge
}
private fun clearBadge() {
val badge = badgeDrawable ?: return
anchor.removeOnLayoutChangeListener(this)
BadgeUtils.detachBadgeDrawable(badge, anchor)
badgeDrawable = null
}
}

View File

@@ -5,7 +5,9 @@ import android.content.res.Resources
import androidx.collection.arraySetOf
import kotlinx.coroutines.CancellationException
import okio.FileNotFoundException
import okio.IOException
import org.acra.ktx.sendWithAcra
import org.json.JSONException
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CaughtException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
@@ -20,6 +22,8 @@ import org.koitharu.kotatsu.parsers.exception.ParseException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
private const val MSG_NO_SPACE_LEFT = "No space left on device"
fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is AuthRequiredException -> resources.getString(R.string.auth_required)
is CloudFlareProtectedException -> resources.getString(R.string.captcha_required)
@@ -41,20 +45,28 @@ fun Throwable.getDisplayMessage(resources: Resources): String = when (this) {
is WrongPasswordException -> resources.getString(R.string.wrong_password)
is NotFoundException -> resources.getString(R.string.not_found_404)
is IOException -> getDisplayMessage(message, resources) ?: localizedMessage
else -> localizedMessage
} ?: resources.getString(R.string.error_occurred)
private fun getDisplayMessage(msg: String?, resources: Resources): String? = when {
msg.isNullOrEmpty() -> null
msg.contains(MSG_NO_SPACE_LEFT) -> resources.getString(R.string.error_no_space_left)
else -> null
}
fun Throwable.isReportable(): Boolean {
return this is Error || this.javaClass in reportableExceptions
}
fun Throwable.report(message: String?) {
val exception = CaughtException(this, message)
fun Throwable.report() {
val exception = CaughtException(this, "${javaClass.simpleName}($message)")
exception.sendWithAcra()
}
private val reportableExceptions = arraySetOf<Class<*>>(
ParseException::class.java,
JSONException::class.java,
RuntimeException::class.java,
IllegalStateException::class.java,
IllegalArgumentException::class.java,

View File

@@ -36,6 +36,7 @@
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingHorizontal="@dimen/list_spacing"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />

View File

@@ -16,7 +16,8 @@
android:id="@+id/checkableGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:layout_marginVertical="16dp"
android:layout_marginHorizontal="?attr/dialogPreferredPadding"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
@@ -50,8 +51,7 @@
style="?materialAlertDialogTitleTextStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="?attr/dialogPreferredPadding"
android:paddingRight="?attr/dialogPreferredPadding"
android:paddingHorizontal="?attr/dialogPreferredPadding"
android:singleLine="true"
android:text="@string/grid_size"
android:visibility="gone"
@@ -72,4 +72,4 @@
tools:visibility="visible" />
</LinearLayout>
</ScrollView>
</ScrollView>

View File

@@ -9,7 +9,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="@dimen/margin_normal"
android:paddingHorizontal="?attr/dialogPreferredPadding"
android:paddingTop="@dimen/margin_normal">
<TextView

View File

@@ -11,10 +11,8 @@
android:id="@+id/textView_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingTop="6dp"
android:paddingEnd="?listPreferredItemPaddingEnd"
android:paddingBottom="6dp"
android:paddingHorizontal="?attr/dialogPreferredPadding"
android:paddingVertical="16dp"
android:textAppearance="?attr/textAppearanceBodyMedium"
tools:text="@string/onboard_text" />
@@ -28,4 +26,4 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_source_locale" />
</LinearLayout>
</LinearLayout>

View File

@@ -6,7 +6,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:padding="20dp">
android:padding="?attr/dialogPreferredPadding">
<TextView
android:id="@+id/textView_title"
@@ -31,4 +31,4 @@
android:textColor="?android:textColorSecondary"
tools:text="Subtitle" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="16dp">
<org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup
android:id="@+id/checkableGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_standard"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/standard"
app:icon="@drawable/ic_book_page" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_reversed"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/right_to_left"
app:icon="@drawable/ic_reader_rtl" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_webtoon"
style="@style/Widget.Kotatsu.ToggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/webtoon"
app:icon="@drawable/ic_script" />
</org.koitharu.kotatsu.base.ui.widgets.CheckableButtonGroup>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/reader_mode_hint"
android:textAppearance="?attr/textAppearanceBodySmall" />
</LinearLayout>

View File

@@ -2,6 +2,7 @@
<com.google.android.material.card.MaterialCardView
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:id="@+id/card_update"
style="@style/Widget.Material3.CardView.Filled"
android:layout_width="match_parent"
@@ -47,6 +48,22 @@
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toBottomOf="@id/textPrimary" />
<TextView
android:id="@+id/textChangelog"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:background="@drawable/bg_badge_empty"
android:fontFamily="monospace"
android:padding="8dp"
android:textAppearance="?attr/textAppearanceBodyMedium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/icon"
app:layout_constraintTop_toBottomOf="@id/textSecondary"
tools:text="- Fixes\n- Improvements" />
<Button
android:id="@+id/button_download"
style="@style/Widget.Material3.Button"
@@ -55,7 +72,7 @@
android:layout_marginTop="12dp"
android:text="@string/download"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/textSecondary" />
app:layout_constraintTop_toBottomOf="@id/textChangelog" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -27,6 +27,7 @@
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="@string/manage" />
</LinearLayout>

View File

@@ -102,15 +102,15 @@
<string name="notification_sound">Suara pemberitahuan</string>
<string name="categories_">Kategori…</string>
<string name="rename">Ubah Nama</string>
<string name="category_delete_confirm">Hapus kategori \"%s\" dari favorit Anda\?
\nSemua manga di situ akan hilang.</string>
<string name="category_delete_confirm">"Hapus kategori \"%s\" dari favorit Anda\?
\nSemua manga disana akan hilang."</string>
<string name="text_empty_holder_primary">Sepi juga di sini…</string>
<string name="text_categories_holder">Anda bisa menggunakan kategori untuk mengelola favorit Anda. Tekan «+» untuk membuat kategori</string>
<string name="text_history_holder_primary">Apa yang Anda baca akan ditampilkan di sini</string>
<string name="text_history_holder_secondary">Cari apa untuk di baca di bilah samping.</string>
<string name="text_local_holder_primary">Simpan sesuatu dulu</string>
<string name="manga_shelf">Rak</string>
<string name="recent_manga">Baru-baru ini</string>
<string name="recent_manga">Terbaru</string>
<string name="pages_animation">Animasi halaman</string>
<string name="manga_save_location">Folder untuk unduhan</string>
<string name="not_available">Tidak tersedia</string>
@@ -298,7 +298,7 @@
<string name="status_completed">Selesai</string>
<string name="canceled">Dibatalkan</string>
<string name="sync_title">Sinkronisasi data Anda</string>
<string name="enter_email_text">Masukkan surel Anda untuk melanjutkan</string>
<string name="enter_email_text">Masukkan email Anda untuk melanjutkan</string>
<string name="tracking">Pelacakan</string>
<string name="logout">Keluar</string>
<string name="sync">Sinkronisasi</string>
@@ -329,7 +329,7 @@
<string name="exit_confirmation_summary">Tekan Kembali dua kali untuk keluar dari aplikasi</string>
<string name="exit_confirmation">Konfirmasi keluar</string>
<string name="pages_cache">Tembolok halaman</string>
<string name="other_cache">Tembolok lainnya</string>
<string name="other_cache">Cache lainnya</string>
<string name="storage_usage">Penggunaan penyimpanan</string>
<string name="available">Tersedia</string>
<string name="incognito_mode">Mode Incognito</string>
@@ -363,9 +363,13 @@
\n Semua manga di sana akan hilang dan ini tidak bisa diurungkan.</string>
<string name="reorder">Atur Ulang</string>
<string name="saved_manga">Manga tersimpan</string>
<string name="confirm_exit">Tekan Kembali lagi untuk keluar</string>
<string name="confirm_exit">Tekan lagi untuk keluar</string>
<string name="no_chapters">Tidak ada bab</string>
<string name="history_shortcuts">Tampilkan pintasan manga baru-baru ini</string>
<string name="history_shortcuts_summary">Buat manga baru-baru ini tersedia dengan menekan panjang pada ikon aplikasi</string>
<string name="select_range">Pilih jangkauan</string>
<string name="disable_all">Matikan semua</string>
<string name="gestures_only">Hanya gestur</string>
<string name="dns_over_https">DNS melalui HTTPS</string>
<string name="status_dropped">Istirahat</string>
</resources>

View File

@@ -375,4 +375,15 @@
<string name="downloading_manga">漫画をダウンロードする</string>
<string name="feed">フィード</string>
<string name="manga_error_description_pattern">エラーの詳細:&lt;br&gt;&lt;tt&gt;%1$s&lt;/tt&gt;&lt;br&gt;&lt;br&gt;1. &lt;a href=%2$s&gt;Webブラウザで漫画を開いてみて&lt;/a&gt;、そのソースで利用できるか確認する&lt;br&gt;2. 利用できる場合は、開発者にエラーレポートを送信する&lt;/a&gt;。</string>
<string name="reader_control_ltr">人間工学に基づいたリーダーコントロール</string>
<string name="history_shortcuts">最近のマンガのショートカットを表示</string>
<string name="history_shortcuts_summary">アプリケーションアイコンを長押しして最近のマンガを利用できるようにする</string>
<string name="reader_control_ltr_summary">右端をタップするか、右キーを押すと、常に次のページに切り替わります</string>
<string name="color_correction">色補正</string>
<string name="brightness">輝度</string>
<string name="contrast">コントラスト</string>
<string name="reset">リセット</string>
<string name="text_unsaved_changes_prompt">未保存の変更がありますが、保存しますか、それとも破棄しますか?</string>
<string name="color_correction_hint">選択した色の設定は、この漫画のために記憶されます</string>
<string name="discard">破棄</string>
</resources>

View File

@@ -43,7 +43,8 @@
</style>
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:colorBackground">@android:color/system_accent1_900</item>
<item name="android:panelColorBackground">@android:color/system_accent1_800</item>
<item name="android:colorBackground">@color/m3_sys_color_dynamic_dark_secondary_container</item>
<item name="android:panelColorBackground">@color/m3_sys_color_dynamic_dark_inverse_primary</item>
</style>
</resources>

View File

@@ -69,7 +69,7 @@
<string name="dont_ask_again">Não pergunte novamente</string>
<string name="cancelling_">Cancelando…</string>
<string name="error">Erro</string>
<string name="clear_thumbs_cache">Limpar cache de thumbnails</string>
<string name="clear_thumbs_cache">Limpar cache de miniaturas</string>
<string name="search_history_cleared">Limpo</string>
<string name="gestures_only">Apenas gestos</string>
<string name="internal_storage">Armazenamento interno</string>
@@ -193,7 +193,7 @@
<string name="vibration">Vibração</string>
<string name="cannot_find_available_storage">Sem armazenamento disponível</string>
<string name="favourites_categories">Categorias favoritas</string>
<string name="category_delete_confirm">Remover a categoria «%s» dos seus favoritos\?
<string name="category_delete_confirm">Remover a categoria \"%s\" dos seus favoritos\?
\nTodos os mangás nela serão perdidos.</string>
<string name="text_history_holder_secondary">Encontre o que ler no menu lateral.</string>
<string name="text_local_holder_secondary">Salve-o de fontes online ou importe arquivos.</string>
@@ -263,4 +263,6 @@
<string name="content">Conteúdo</string>
<string name="chapters_empty">Não há capítulos nesta manga</string>
<string name="percent_string_pattern">%1$s%%</string>
<string name="text_shelf_holder_primary">Seu mangá será exibido aqui</string>
<string name="color_correction">Correção de cor</string>
</resources>

View File

@@ -43,8 +43,8 @@
</style>
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@android:style/Theme.DeviceDefault.DayNight">
<item name="android:colorBackground">@android:color/system_accent1_50</item>
<item name="android:panelColorBackground">@android:color/system_accent1_100</item>
<item name="android:colorBackground">@color/m3_sys_color_dynamic_light_secondary_container</item>
<item name="android:panelColorBackground">@color/m3_sys_color_dynamic_light_inverse_primary</item>
</style>
</resources>

View File

@@ -7,7 +7,7 @@
<item quantity="other">%1$d 个新章节</item>
</plurals>
<plurals name="chapters">
<item quantity="other">%1$d 章节</item>
<item quantity="other">%1$d 个章节</item>
</plurals>
<plurals name="chapters_from_x">
<item quantity="other">来自 %2$d 中的 %1$d 个章节</item>

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="show_notification_new_chapters_off">你将不会收到通知,但新的章节将在列表中突出显示</string>
<string name="computing_">计算…</string>
<string name="try_again">再试一次</string>
<string name="history_is_empty">尚无历史记录</string>
<string name="operation_not_supported">不支持这种操作</string>
<string name="application_update">检查应用程序的新版本</string>
<string name="text_shelf_holder_secondary">在 \"探索 \"部分找到要读的内容</string>
<string name="text_local_holder_secondary">从在线来源保存或导入文件。</string>
<string name="text_history_holder_primary">你所读的内容将在这里显示</string>
<string name="text_history_holder_secondary">在侧面菜单中找到要读的内容。</string>
<string name="text_shelf_holder_primary">你的漫画将显示在这里</string>
<string name="text_empty_holder_primary">这里有点空…</string>
<string name="feed_will_update_soon">饲料更新将很快开始</string>
<string name="reader_mode_hint">所选择的配置将因这部漫画而被记住</string>
<string name="data_restored_with_errors">数据被恢复了,但有错误</string>
<string name="black_dark_theme_summary">在AMOLED屏幕上使用更少的电力</string>
<string name="auth_required">登录后可查看此内容</string>
<string name="text_clear_search_history_prompt">永久地删除所有最近的搜索查询?</string>
<string name="text_clear_updates_feed_prompt">永久地清除所有的更新历史?</string>
<string name="text_clear_cookies_prompt">你将从所有来源中注销</string>
<string name="exclude_nsfw_from_history">從歷史中排除NSFW漫畫</string>
<string name="dynamic_theme_summary">应用根据你的墙纸的颜色方案创建的主题</string>
<string name="text_suggestion_holder">开始阅读漫画,你会得到个性化的建议</string>
<string name="suggestions_summary">根据你的喜好推荐漫画</string>
<string name="error_empty_name">你必须输入一个名称</string>
<string name="suggestions_excluded_genres_summary">指定您不希望在建议中看到的体裁</string>
<string name="text_delete_local_manga_batch">从设备中永久删除选定的项目?</string>
<string name="filter_load_error">无法加载流派列表</string>
<string name="chapters_will_removed_background">章节将在后台被删除。这可能需要一些时间</string>
<string name="check_new_chapters_title">检查新的章节并通知有关情况</string>
<string name="download_slowdown_summary">有助于避免阻断你的IP地址</string>
<string name="email_enter_hint">输入你的电子邮件以继续</string>
<string name="new_sources_text">有了新的漫画来源</string>
<string name="detect_reader_mode_summary">自动检测漫画是否为网络漫画</string>
<string name="show_reading_indicators_summary">在历史和收藏夹中显示阅读百分比</string>
<string name="confirm_exit">再次按 \"返回 \"键退出</string>
<string name="local_storage">本地存储</string>
<string name="reader_control_ltr_summary">轻敲右边缘或按右键总是切换到下一页</string>
<string name="text_unsaved_changes_prompt">你有未保存的修改,你想保存还是丢弃它们?</string>
<string name="import_completed_hint">您可以從儲存中刪除原始檔案以節省空間</string>
<string name="close_menu">关闭菜单</string>
<string name="favourites">最爱</string>
<string name="chapters">章节</string>
<string name="list">列表</string>
<string name="grid">栅格</string>
<string name="settings">设置</string>
<string name="nothing_found">没有发现</string>
<string name="onboard_text">选择你想看的漫画的语言。你可以在以后的设置中改变它。</string>
<string name="clear_cookies_summary">可以在出现一些问题时提供帮助。所有授权将被视为无效</string>
<string name="open_menu">打开菜单</string>
<string name="history">历史</string>
<string name="error_occurred">发生了一个错误</string>
<string name="network_error">无法连接到互联网</string>
<string name="details">详情</string>
<string name="chapter_d_of_d">第%1$d 。%2$d</string>
<string name="detailed_list">详细列表</string>
<string name="list_mode">列表模式</string>
<string name="remote_sources">远程资源</string>
<string name="loading_">正在加载…</string>
<string name="close">关闭</string>
<string name="clear_history">清除历史</string>
<string name="text_categories_holder">你可以使用类别来组织你的收藏夹。按 \"+\"来创建一个类别</string>
<string name="suggestions_info">所有的数据都在这个设备上进行本地分析。您的个人数据不会被转移到任何服务机构。</string>
<string name="exclude_nsfw_from_history_summary">标记为NSFW的漫画将永远不会被添加到历史中,你的进度也不会被保存</string>
<string name="backup_information">你可以创建你的历史和收藏的备份并恢复它</string>
<string name="crash_text">出了点问题。请向开发人员提交一份错误报告,以帮助我们修复它。</string>
<string name="category_delete_confirm">从你的收藏夹中删除 \"%s\" 类别?
\n
\n里面的所有漫画都会丢失。</string>
<string name="color_correction_hint">所选择的颜色设置将被铭记在这部漫画中</string>
<string name="show_notification_app_update">如果有新版本,显示通知</string>
<string name="text_file_not_supported">要么选择ZIP或CBZ文件。</string>
<string name="no_bookmarks_summary">你可以在阅读漫画时创建书签</string>
<string name="show_notification_new_chapters_on">你会收到你正在阅读的漫画的更新通知</string>
<string name="tracker_warning">一些设备有不同的系统行为,这可能会破坏后台任务。</string>
<string name="protect_application_subtitle">输入密码以启动应用程序</string>
<string name="chapters_checking_progress">检查新的章节:%1$d of%2$d</string>
<string name="chapter_is_missing_text">下载或在线阅读这缺失的章节。</string>
<string name="protect_application_summary">在启动Kotatsu时要求输入密码</string>
<string name="chapters_empty">这部漫画中没有章节</string>
<string name="text_feed_holder">你正在阅读的新章节显示在这里</string>
<string name="history_shortcuts_summary">通过长按应用程序图标来提供最近的漫画</string>
<string name="large_manga_save_confirm">这部漫画有%s 。全部保存?</string>
<string name="network_consumption_warning">这可能会传输大量的数据</string>
<string name="app_update_available">新版本的应用程序已经推出</string>
<string name="password_length_hint">密码必须是4个字符或以上</string>
<string name="auth_not_supported_by">不支持在%s 上登录</string>
<string name="_s_deleted_from_local_storage">\"%s\" 从本地存储中删除</string>
<string name="exit_confirmation_summary">按两次 \"返回 \"键,退出应用程序</string>
<string name="no_manga_sources_text">启用漫画来源,在线阅读漫画</string>
<string name="text_clear_history_prompt">永久清除所有阅读历史?</string>
<string name="wait_for_loading_finish">等待加载完成…</string>
<string name="text_delete_local_manga">从设备中永久删除 \"%s\"?</string>
<string name="text_search_holder_secondary">尝试重新表述查询。</string>
<string name="disable_battery_optimization_summary">帮助进行背景更新检查</string>
<string name="enter_email_text">输入你的电子邮件以继续</string>
<string name="importing_progress">进口漫画:%1$d 的%2$d</string>
<string name="exclude_nsfw_from_suggestions">请勿推荐NSFW漫画</string>
<string name="update_check_failed">无法查找更新</string>
<string name="add_new_category">新类别</string>
<string name="read">阅读</string>
<string name="you_have_not_favourites_yet">暂时没有喜欢的人</string>
<string name="add_to_favourites">最喜欢这个</string>
<string name="add">添加</string>
</resources>

View File

@@ -388,4 +388,6 @@
<string name="color_correction_hint">The chosen color settings will be remembered for this manga</string>
<string name="text_unsaved_changes_prompt">You have unsaved changes, do you want to save or discard them?</string>
<string name="discard">Discard</string>
<string name="error_no_space_left">No space left on device</string>
<string name="reader_slider">Show page switching slider</string>
</resources>

View File

@@ -45,6 +45,11 @@
android:key="reader_bar"
android:title="@string/reader_info_bar" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="reader_slider"
android:title="@string/reader_slider" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pages_numbers"

View File

@@ -7,7 +7,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:7.3.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.43.2'
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.44'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files