Merge branch 'devel'

pull/185/head v3.3.2
Koitharu 4 years ago
commit 5192175ddc
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -44,7 +44,7 @@ body:
label: Kotatsu version label: Kotatsu version
description: You can find your Kotatsu version in **Settings → About**. description: You can find your Kotatsu version in **Settings → About**.
placeholder: | placeholder: |
Example: "3.3" Example: "3.3.1"
validations: validations:
required: true required: true
@ -85,9 +85,9 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/nv95/kotatsu-parsers/issues/new). - label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers/issues/new).
required: true required: true
- label: I have updated the app to version **[3.3](https://github.com/nv95/Kotatsu/releases/latest)**. - label: I have updated the app to version **[3.3.1](https://github.com/KotatsuApp/Kotatsu/releases/latest)**.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

@ -31,9 +31,9 @@ body:
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/nv95/kotatsu-parsers/issues/new). - label: If this is an issue with a source, I should be opening an issue in the [parsers repository](https://github.com/KotatsuApp/kotatsu-parsers/issues/new).
required: true required: true
- label: I have updated the app to version **[3.3](https://github.com/nv95/Kotatsu/releases/latest)**. - label: I have updated the app to version **[3.3.1](https://github.com/KotatsuApp/Kotatsu/releases/latest)**.
required: true required: true
- label: I will fill out all of the requested information in this form. - label: I will fill out all of the requested information in this form.
required: true required: true

@ -10,7 +10,7 @@ Kotatsu is a free and open source manga reader for Android.
alt="Get it on F-Droid" alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/org.koitharu.kotatsu) height="80">](https://f-droid.org/packages/org.koitharu.kotatsu)
Download APK from Github Releases: Download APK from GitHub Releases:
- [Latest release](https://github.com/KotatsuApp/Kotatsu/releases/latest) - [Latest release](https://github.com/KotatsuApp/Kotatsu/releases/latest)

@ -14,8 +14,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 32 targetSdkVersion 32
versionCode 410 versionCode 411
versionName '3.3.1' versionName '3.3.2'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -73,7 +73,7 @@ afterEvaluate {
} }
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation('com.github.nv95:kotatsu-parsers:8a3b6df91d') { implementation('com.github.nv95:kotatsu-parsers:c92f89f307') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
@ -82,19 +82,20 @@ dependencies {
implementation 'androidx.core:core-ktx:1.8.0' implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.activity:activity-ktx:1.5.0-rc01' implementation 'androidx.activity:activity-ktx:1.5.0-rc01'
implementation 'androidx.fragment:fragment-ktx:1.5.0-rc01' implementation 'androidx.fragment:fragment-ktx:1.5.0-rc01'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc01' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.0-rc02'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0-rc01' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.0-rc02'
implementation 'androidx.lifecycle:lifecycle-service:2.5.0-rc01' implementation 'androidx.lifecycle:lifecycle-service:2.5.0-rc02'
implementation 'androidx.lifecycle:lifecycle-process:2.5.0-rc01' implementation 'androidx.lifecycle:lifecycle-process:2.5.0-rc02'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.preference:preference-ktx:1.2.0'
implementation 'androidx.work:work-runtime-ktx:2.7.1' implementation 'androidx.work:work-runtime-ktx:2.7.1'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
implementation 'com.google.android.material:material:1.7.0-alpha02' implementation 'com.google.android.material:material:1.7.0-alpha02'
//noinspection LifecycleAnnotationProcessorWithJava8 //noinspection LifecycleAnnotationProcessorWithJava8
kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0-rc01' kapt 'androidx.lifecycle:lifecycle-compiler:2.5.0-rc02'
implementation 'androidx.room:room-runtime:2.4.2' implementation 'androidx.room:room-runtime:2.4.2'
implementation 'androidx.room:room-ktx:2.4.2' implementation 'androidx.room:room-ktx:2.4.2'

@ -6,14 +6,12 @@ import androidx.annotation.CallSuper
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.SettingsHeadersFragment import org.koitharu.kotatsu.settings.SettingsHeadersFragment
abstract class BasePreferenceFragment(@StringRes private val titleId: Int) : abstract class BasePreferenceFragment(@StringRes private val titleId: Int) :

@ -1,89 +0,0 @@
package org.koitharu.kotatsu.base.ui.dialog
import android.content.Context
import android.content.DialogInterface
import android.text.InputFilter
import android.view.LayoutInflater
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.databinding.DialogInputBinding
class TextInputDialog private constructor(
private val delegate: AlertDialog,
) : DialogInterface by delegate {
fun show() = delegate.show()
class Builder(context: Context) {
private val binding = DialogInputBinding.inflate(LayoutInflater.from(context))
private val delegate = MaterialAlertDialogBuilder(context)
.setView(binding.root)
fun setTitle(@StringRes titleResId: Int): Builder {
delegate.setTitle(titleResId)
return this
}
fun setTitle(title: CharSequence): Builder {
delegate.setTitle(title)
return this
}
fun setHint(@StringRes hintResId: Int): Builder {
binding.inputEdit.hint = binding.root.context.getString(hintResId)
return this
}
fun setMaxLength(maxLength: Int, strict: Boolean): Builder {
with(binding.inputLayout) {
counterMaxLength = maxLength
isCounterEnabled = maxLength > 0
}
if (strict && maxLength > 0) {
binding.inputEdit.filters += InputFilter.LengthFilter(maxLength)
}
return this
}
fun setInputType(inputType: Int): Builder {
binding.inputEdit.inputType = inputType
return this
}
fun setText(text: String): Builder {
binding.inputEdit.setText(text)
binding.inputEdit.setSelection(text.length)
return this
}
fun setPositiveButton(
@StringRes textId: Int,
listener: (DialogInterface, String) -> Unit
): Builder {
delegate.setPositiveButton(textId) { dialog, _ ->
listener(dialog, binding.inputEdit.text?.toString().orEmpty())
}
return this
}
fun setNegativeButton(
@StringRes textId: Int,
listener: DialogInterface.OnClickListener? = null
): Builder {
delegate.setNegativeButton(textId, listener)
return this
}
fun setOnCancelListener(listener: DialogInterface.OnCancelListener): Builder {
delegate.setOnCancelListener(listener)
return this
}
fun create() =
TextInputDialog(delegate.create())
}
}

@ -15,11 +15,11 @@ class BackupZipOutput(val file: File) : Closeable {
private val output = ZipOutput(file, Deflater.BEST_COMPRESSION) private val output = ZipOutput(file, Deflater.BEST_COMPRESSION)
suspend fun put(entry: BackupEntry) { suspend fun put(entry: BackupEntry) = runInterruptible(Dispatchers.IO) {
output.put(entry.name, entry.data.toString(2)) output.put(entry.name, entry.data.toString(2))
} }
suspend fun finish() { suspend fun finish() = runInterruptible(Dispatchers.IO) {
output.finish() output.finish()
} }

@ -1,8 +1,6 @@
package org.koitharu.kotatsu.core.exceptions package org.koitharu.kotatsu.core.exceptions
import androidx.annotation.StringRes
import okio.IOException import okio.IOException
import org.koitharu.kotatsu.R
class CloudFlareProtectedException( class CloudFlareProtectedException(
val url: String val url: String

@ -40,7 +40,7 @@ class AppSettings(context: Context) {
get() = Collections.unmodifiableSet(remoteSources) get() = Collections.unmodifiableSet(remoteSources)
var listMode: ListMode var listMode: ListMode
get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.DETAILED_LIST) get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.GRID)
set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) } set(value) = prefs.edit { putEnumValue(KEY_LIST_MODE, value) }
var defaultSection: AppSection var defaultSection: AppSection
@ -125,6 +125,10 @@ class AppSettings(context: Context) {
get() = prefs.getString(KEY_APP_PASSWORD, null) get() = prefs.getString(KEY_APP_PASSWORD, null)
set(value) = prefs.edit { if (value != null) putString(KEY_APP_PASSWORD, value) else remove(KEY_APP_PASSWORD) } set(value) = prefs.edit { if (value != null) putString(KEY_APP_PASSWORD, value) else remove(KEY_APP_PASSWORD) }
var isBiometricProtectionEnabled: Boolean
get() = prefs.getBoolean(KEY_PROTECT_APP_BIOMETRIC, true)
set(value) = prefs.edit { putBoolean(KEY_PROTECT_APP_BIOMETRIC, value) }
var sourcesOrder: List<String> var sourcesOrder: List<String>
get() = prefs.getString(KEY_SOURCES_ORDER, null) get() = prefs.getString(KEY_SOURCES_ORDER, null)
?.split('|') ?.split('|')
@ -293,6 +297,7 @@ class AppSettings(context: Context) {
const val KEY_READER_MODE_DETECT = "reader_mode_detect" const val KEY_READER_MODE_DETECT = "reader_mode_detect"
const val KEY_APP_PASSWORD = "app_password" const val KEY_APP_PASSWORD = "app_password"
const val KEY_PROTECT_APP = "protect_app" const val KEY_PROTECT_APP = "protect_app"
const val KEY_PROTECT_APP_BIOMETRIC = "protect_app_bio"
const val KEY_APP_VERSION = "app_version" const val KEY_APP_VERSION = "app_version"
const val KEY_ZOOM_MODE = "zoom_mode" const val KEY_ZOOM_MODE = "zoom_mode"
const val KEY_BACKUP = "backup" const val KEY_BACKUP = "backup"
@ -316,9 +321,6 @@ class AppSettings(context: Context) {
const val KEY_APP_UPDATE = "app_update" const val KEY_APP_UPDATE = "app_update"
const val KEY_APP_UPDATE_AUTO = "app_update_auto" const val KEY_APP_UPDATE_AUTO = "app_update_auto"
const val KEY_APP_TRANSLATION = "about_app_translation" const val KEY_APP_TRANSLATION = "about_app_translation"
const val KEY_FEEDBACK_4PDA = "about_feedback_4pda"
const val KEY_FEEDBACK_DISCORD = "about_feedback_discord"
const val KEY_FEEDBACK_GITHUB = "about_feedback_github"
private const val NETWORK_NEVER = 0 private const val NETWORK_NEVER = 0
private const val NETWORK_ALWAYS = 1 private const val NETWORK_ALWAYS = 1

@ -4,6 +4,7 @@ import android.content.Context
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.FavouriteCategory import org.koitharu.kotatsu.core.model.FavouriteCategory
import com.google.android.material.R as materialR
class CategoriesEditDelegate( class CategoriesEditDelegate(
private val context: Context, private val context: Context,
@ -11,9 +12,10 @@ class CategoriesEditDelegate(
) { ) {
fun deleteCategory(category: FavouriteCategory) { fun deleteCategory(category: FavouriteCategory) {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
.setMessage(context.getString(R.string.category_delete_confirm, category.title)) .setMessage(context.getString(R.string.category_delete_confirm, category.title))
.setTitle(R.string.remove_category) .setTitle(R.string.remove_category)
.setIcon(R.drawable.ic_delete)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.remove) { _, _ -> .setPositiveButton(R.string.remove) { _, _ ->
callback.onDeleteCategory(category) callback.onDeleteCategory(category)

@ -3,8 +3,6 @@ package org.koitharu.kotatsu.favourites.ui.categories.edit
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
@ -24,7 +22,8 @@ import org.koitharu.kotatsu.favourites.ui.categories.CategoriesActivity
import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>(), AdapterView.OnItemClickListener { class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>(), AdapterView.OnItemClickListener,
View.OnClickListener {
private val viewModel by viewModel<FavouritesCategoryEditViewModel> { private val viewModel by viewModel<FavouritesCategoryEditViewModel> {
parametersOf(intent.getLongExtra(EXTRA_ID, NO_ID)) parametersOf(intent.getLongExtra(EXTRA_ID, NO_ID))
@ -39,6 +38,7 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
setHomeAsUpIndicator(com.google.android.material.R.drawable.abc_ic_clear_material) setHomeAsUpIndicator(com.google.android.material.R.drawable.abc_ic_clear_material)
} }
initSortSpinner() initSortSpinner()
binding.buttonDone.setOnClickListener(this)
viewModel.onSaved.observe(this) { finishAfterTransition() } viewModel.onSaved.observe(this) { finishAfterTransition() }
viewModel.category.observe(this, ::onCategoryChanged) viewModel.category.observe(this, ::onCategoryChanged)
@ -62,22 +62,14 @@ class FavouritesCategoryEditActivity : BaseActivity<ActivityCategoryEditBinding>
} }
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onClick(v: View) {
menuInflater.inflate(R.menu.opt_config, menu) when (v.id) {
menu.findItem(R.id.action_done)?.setTitle(R.string.save) R.id.button_done -> viewModel.save(
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_done -> {
viewModel.save(
title = binding.editName.text?.toString().orEmpty(), title = binding.editName.text?.toString().orEmpty(),
sortOrder = getSelectedSortOrder(), sortOrder = getSelectedSortOrder(),
isTrackerEnabled = binding.switchTracker.isChecked, isTrackerEnabled = binding.switchTracker.isChecked,
) )
true
} }
else -> super.onOptionsItemSelected(item)
} }
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {

@ -2,11 +2,9 @@ package org.koitharu.kotatsu.favourites.ui.categories.select
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@ -28,7 +26,7 @@ class FavouriteCategoriesBottomSheet :
BaseBottomSheet<DialogFavoriteCategoriesBinding>(), BaseBottomSheet<DialogFavoriteCategoriesBinding>(),
OnListItemClickListener<MangaCategoryItem>, OnListItemClickListener<MangaCategoryItem>,
CategoriesEditDelegate.CategoriesEditCallback, CategoriesEditDelegate.CategoriesEditCallback,
Toolbar.OnMenuItemClickListener, View.OnClickListener { View.OnClickListener {
private val viewModel by viewModel<MangaCategoriesViewModel> { private val viewModel by viewModel<MangaCategoriesViewModel> {
parametersOf(requireNotNull(arguments?.getParcelableArrayList<ParcelableManga>(KEY_MANGA_LIST)).map { it.manga }) parametersOf(requireNotNull(arguments?.getParcelableArrayList<ParcelableManga>(KEY_MANGA_LIST)).map { it.manga })
@ -45,7 +43,7 @@ class FavouriteCategoriesBottomSheet :
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = MangaCategoriesAdapter(this) adapter = MangaCategoriesAdapter(this)
binding.recyclerViewCategories.adapter = adapter binding.recyclerViewCategories.adapter = adapter
binding.toolbar.setOnMenuItemClickListener(this) binding.buttonDone.setOnClickListener(this)
binding.itemCreate.setOnClickListener(this) binding.itemCreate.setOnClickListener(this)
viewModel.content.observe(viewLifecycleOwner, this::onContentChanged) viewModel.content.observe(viewLifecycleOwner, this::onContentChanged)
@ -57,19 +55,10 @@ class FavouriteCategoriesBottomSheet :
super.onDestroyView() super.onDestroyView()
} }
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_done -> {
dismiss()
true
}
else -> false
}
}
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.item_create -> startActivity(FavouritesCategoryEditActivity.newIntent(requireContext())) R.id.item_create -> startActivity(FavouritesCategoryEditActivity.newIntent(requireContext()))
R.id.button_done -> dismiss()
} }
} }

@ -7,6 +7,7 @@ import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import com.google.android.material.R as materialR
class HistoryListMenuProvider( class HistoryListMenuProvider(
private val context: Context, private val context: Context,
@ -19,9 +20,10 @@ class HistoryListMenuProvider(
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) { override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_clear_history -> { R.id.action_clear_history -> {
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
.setTitle(R.string.clear_history) .setTitle(R.string.clear_history)
.setMessage(R.string.text_clear_history_prompt) .setMessage(R.string.text_clear_history_prompt)
.setIcon(R.drawable.ic_delete)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.clear) { _, _ -> .setPositiveButton(R.string.clear) { _, _ ->
viewModel.clearHistory() viewModel.clearHistory()

@ -9,7 +9,6 @@ import androidx.collection.ArraySet
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isNotEmpty import androidx.core.view.isNotEmpty
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar

@ -299,8 +299,9 @@ class MainActivity :
} }
override fun onClearSearchHistory() { override fun onClearSearchHistory() {
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this, materialR.style.ThemeOverlay_Material3_MaterialAlertDialog_Centered)
.setTitle(R.string.clear_search_history) .setTitle(R.string.clear_search_history)
.setIcon(R.drawable.ic_clear_all)
.setMessage(R.string.text_clear_search_history_prompt) .setMessage(R.string.text_clear_search_history_prompt)
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.clear) { _, _ -> .setPositiveButton(R.string.clear) { _, _ ->

@ -10,6 +10,11 @@ import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.AuthenticationCallback
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -17,8 +22,11 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityProtectBinding import org.koitharu.kotatsu.databinding.ActivityProtectBinding
import org.koitharu.kotatsu.utils.ext.getDisplayMessage import org.koitharu.kotatsu.utils.ext.getDisplayMessage
class ProtectActivity : BaseActivity<ActivityProtectBinding>(), TextView.OnEditorActionListener, class ProtectActivity :
TextWatcher, View.OnClickListener { BaseActivity<ActivityProtectBinding>(),
TextView.OnEditorActionListener,
TextWatcher,
View.OnClickListener {
private val viewModel by viewModel<ProtectViewModel>() private val viewModel by viewModel<ProtectViewModel>()
@ -39,8 +47,10 @@ class ProtectActivity : BaseActivity<ActivityProtectBinding>(), TextView.OnEdito
finishAfterTransition() finishAfterTransition()
} }
if (!useFingerprint()) {
binding.editPassword.requestFocus() binding.editPassword.requestFocus()
} }
}
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
val basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding) val basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)
@ -85,6 +95,31 @@ class ProtectActivity : BaseActivity<ActivityProtectBinding>(), TextView.OnEdito
binding.layoutPassword.isEnabled = !isLoading binding.layoutPassword.isEnabled = !isLoading
} }
private fun useFingerprint(): Boolean {
if (!viewModel.isBiometricEnabled) {
return false
}
if (BiometricManager.from(this).canAuthenticate(BIOMETRIC_WEAK) != BIOMETRIC_SUCCESS) {
return false
}
val prompt = BiometricPrompt(this, BiometricCallback())
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setAllowedAuthenticators(BIOMETRIC_WEAK)
.setTitle(getString(R.string.app_name))
.setConfirmationRequired(false)
.setNegativeButtonText(getString(android.R.string.cancel))
.build()
prompt.authenticate(promptInfo)
return true
}
private inner class BiometricCallback : AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
viewModel.unlock()
}
}
companion object { companion object {
private const val EXTRA_INTENT = "src_intent" private const val EXTRA_INTENT = "src_intent"

@ -19,6 +19,9 @@ class ProtectViewModel(
val onUnlockSuccess = SingleLiveEvent<Unit>() val onUnlockSuccess = SingleLiveEvent<Unit>()
val isBiometricEnabled
get() = settings.isBiometricProtectionEnabled
fun tryUnlock(password: String) { fun tryUnlock(password: String) {
if (job?.isActive == true) { if (job?.isActive == true) {
return return
@ -27,12 +30,16 @@ class ProtectViewModel(
val passwordHash = password.md5() val passwordHash = password.md5()
val appPasswordHash = settings.appPassword val appPasswordHash = settings.appPassword
if (passwordHash == appPasswordHash) { if (passwordHash == appPasswordHash) {
protectHelper.unlock() unlock()
onUnlockSuccess.call(Unit)
} else { } else {
delay(PASSWORD_COMPARE_DELAY) delay(PASSWORD_COMPARE_DELAY)
throw WrongPasswordException() throw WrongPasswordException()
} }
} }
} }
fun unlock() {
protectHelper.unlock()
onUnlockSuccess.call(Unit)
}
} }

@ -16,6 +16,7 @@ abstract class BasePageHolder<B : ViewBinding>(
exceptionResolver: ExceptionResolver exceptionResolver: ExceptionResolver
) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback { ) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback {
@Suppress("LeakingThis")
protected val delegate = PageHolderDelegate(loader, settings, this, exceptionResolver) protected val delegate = PageHolderDelegate(loader, settings, this, exceptionResolver)
protected val bindingInfo = LayoutPageInfoBinding.bind(binding.root) protected val bindingInfo = LayoutPageInfoBinding.bind(binding.root)

@ -11,10 +11,11 @@ import org.koitharu.kotatsu.utils.ext.resetTransformations
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@Suppress("LeakingThis")
abstract class BaseReaderAdapter<H : BasePageHolder<*>>( abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
private val loader: PageLoader, private val loader: PageLoader,
private val settings: AppSettings, private val settings: AppSettings,
private val exceptionResolver: ExceptionResolver private val exceptionResolver: ExceptionResolver,
) : RecyclerView.Adapter<H>() { ) : RecyclerView.Adapter<H>() {
private val differ = AsyncListDiffer(this, DiffCallback()) private val differ = AsyncListDiffer(this, DiffCallback())

@ -3,7 +3,6 @@ package org.koitharu.kotatsu.search.ui
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.graphics.Insets import androidx.core.graphics.Insets

@ -89,7 +89,7 @@ class SettingsActivity :
val fm = supportFragmentManager val fm = supportFragmentManager
val fragment = fm.fragmentFactory.instantiate(classLoader, pref.fragment ?: return false) val fragment = fm.fragmentFactory.instantiate(classLoader, pref.fragment ?: return false)
fragment.arguments = pref.extras fragment.arguments = pref.extras
// fragment.setTargetFragment(caller, 0) fragment.setTargetFragment(caller, 0)
openFragment(fragment) openFragment(fragment)
return true return true
} }
@ -118,6 +118,7 @@ class SettingsActivity :
val fragment = when (intent?.action) { val fragment = when (intent?.action) {
ACTION_READER -> ReaderSettingsFragment() ACTION_READER -> ReaderSettingsFragment()
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment() ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
ACTION_TRACKER -> TrackerSettingsFragment()
ACTION_SOURCE -> SourceSettingsFragment.newInstance( ACTION_SOURCE -> SourceSettingsFragment.newInstance(
intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL
) )
@ -133,6 +134,7 @@ class SettingsActivity :
private const val ACTION_READER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS" private const val ACTION_READER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
private const val ACTION_SUGGESTIONS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS" private const val ACTION_SUGGESTIONS = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SUGGESTIONS"
private const val ACTION_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER"
private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS" private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
private const val EXTRA_SOURCE = "source" private const val EXTRA_SOURCE = "source"
@ -146,6 +148,10 @@ class SettingsActivity :
Intent(context, SettingsActivity::class.java) Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SUGGESTIONS) .setAction(ACTION_SUGGESTIONS)
fun newTrackerSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_TRACKER)
fun newSourceSettingsIntent(context: Context, source: MangaSource) = fun newSourceSettingsIntent(context: Context, source: MangaSource) =
Intent(context, SettingsActivity::class.java) Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SOURCE) .setAction(ACTION_SOURCE)

@ -1,5 +1,7 @@
package org.koitharu.kotatsu.settings.protect package org.koitharu.kotatsu.settings.protect
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.text.Editable import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
@ -7,9 +9,11 @@ import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.CompoundButton
import android.widget.TextView import android.widget.TextView
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
@ -18,7 +22,7 @@ import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding
private const val MIN_PASSWORD_LENGTH = 4 private const val MIN_PASSWORD_LENGTH = 4
class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWatcher, class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWatcher,
View.OnClickListener, TextView.OnEditorActionListener { View.OnClickListener, TextView.OnEditorActionListener, CompoundButton.OnCheckedChangeListener {
private val viewModel by viewModel<ProtectSetupViewModel>() private val viewModel by viewModel<ProtectSetupViewModel>()
@ -31,6 +35,9 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
binding.buttonNext.setOnClickListener(this) binding.buttonNext.setOnClickListener(this)
binding.buttonCancel.setOnClickListener(this) binding.buttonCancel.setOnClickListener(this)
binding.switchBiometric.isChecked = viewModel.isBiometricEnabled
binding.switchBiometric.setOnCheckedChangeListener(this)
viewModel.isSecondStep.observe(this, this::onStepChanged) viewModel.isSecondStep.observe(this, this::onStepChanged)
viewModel.onPasswordSet.observe(this) { viewModel.onPasswordSet.observe(this) {
finishAfterTransition() finishAfterTransition()
@ -62,6 +69,10 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
} }
} }
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
viewModel.setBiometricEnabled(isChecked)
}
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
return if (actionId == EditorInfo.IME_ACTION_DONE && binding.buttonNext.isEnabled) { return if (actionId == EditorInfo.IME_ACTION_DONE && binding.buttonNext.isEnabled) {
binding.buttonNext.performClick() binding.buttonNext.performClick()
@ -85,6 +96,7 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
private fun onStepChanged(isSecondStep: Boolean) { private fun onStepChanged(isSecondStep: Boolean) {
binding.buttonCancel.isGone = isSecondStep binding.buttonCancel.isGone = isSecondStep
binding.switchBiometric.isVisible = isSecondStep && isBiometricAvailable()
if (isSecondStep) { if (isSecondStep) {
binding.layoutPassword.helperText = getString(R.string.repeat_password) binding.layoutPassword.helperText = getString(R.string.repeat_password)
binding.buttonNext.setText(R.string.confirm) binding.buttonNext.setText(R.string.confirm)
@ -93,4 +105,9 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
binding.buttonNext.setText(R.string.next) binding.buttonNext.setText(R.string.next)
} }
} }
private fun isBiometricAvailable(): Boolean {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)
}
} }

@ -22,6 +22,9 @@ class ProtectSetupViewModel(
val onPasswordMismatch = SingleLiveEvent<Unit>() val onPasswordMismatch = SingleLiveEvent<Unit>()
val onClearText = SingleLiveEvent<Unit>() val onClearText = SingleLiveEvent<Unit>()
val isBiometricEnabled
get() = settings.isBiometricProtectionEnabled
fun onNextClick(password: String) { fun onNextClick(password: String) {
if (firstPassword.value == null) { if (firstPassword.value == null) {
firstPassword.value = password firstPassword.value = password
@ -35,4 +38,8 @@ class ProtectSetupViewModel(
} }
} }
} }
fun setBiometricEnabled(isEnabled: Boolean) {
settings.isBiometricProtectionEnabled = isEnabled
}
} }

@ -106,7 +106,13 @@ class SourcesSettingsFragment :
searchView.queryHint = searchMenuItem.title searchView.queryHint = searchMenuItem.title
} }
override fun onMenuItemSelected(menuItem: MenuItem): Boolean = false override fun onMenuItemSelected(menuItem: MenuItem): Boolean = when (menuItem.itemId) {
R.id.action_disable_all -> {
viewModel.disableAll()
true
}
else -> false
}
override fun onMenuItemActionExpand(item: MenuItem?): Boolean { override fun onMenuItemActionExpand(item: MenuItem?): Boolean {
(activity as? AppBarOwner)?.appBar?.setExpanded(false, true) (activity as? AppBarOwner)?.appBar?.setExpanded(false, true)

@ -7,6 +7,7 @@ import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.core.model.getLocaleTitle import org.koitharu.kotatsu.core.model.getLocaleTitle
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
import org.koitharu.kotatsu.utils.ext.map import org.koitharu.kotatsu.utils.ext.map
@ -58,6 +59,13 @@ class SourcesSettingsViewModel(
buildList() buildList()
} }
fun disableAll() {
settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet {
it.name
}
buildList()
}
fun expandOrCollapse(headerId: String?) { fun expandOrCollapse(headerId: String?) {
if (headerId in expandedGroups) { if (headerId in expandedGroups) {
expandedGroups.remove(headerId) expandedGroups.remove(headerId)

@ -29,6 +29,6 @@ class RingtonePickContract(private val title: String?) : ActivityResultContract<
} }
override fun parseResult(resultCode: Int, intent: Intent?): Uri? { override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
return intent?.getParcelableExtra<Uri>(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) return intent?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
} }
} }

@ -32,14 +32,14 @@ class SuggestionRepository(
suspend fun replace(suggestions: Iterable<MangaSuggestion>) { suspend fun replace(suggestions: Iterable<MangaSuggestion>) {
db.withTransaction { db.withTransaction {
db.suggestionDao.deleteAll() db.suggestionDao.deleteAll()
suggestions.forEach { x -> suggestions.forEach { (manga, relevance) ->
val tags = x.manga.tags.toEntities() val tags = manga.tags.toEntities()
db.tagsDao.upsert(tags) db.tagsDao.upsert(tags)
db.mangaDao.upsert(x.manga.toEntity(), tags) db.mangaDao.upsert(manga.toEntity(), tags)
db.suggestionDao.upsert( db.suggestionDao.upsert(
SuggestionEntity( SuggestionEntity(
mangaId = x.manga.id, mangaId = manga.id,
relevance = x.relevance, relevance = relevance,
createdAt = System.currentTimeMillis(), createdAt = System.currentTimeMillis(),
) )
) )

@ -9,6 +9,7 @@ import androidx.core.view.MenuProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.tracker.work.TrackWorker import org.koitharu.kotatsu.tracker.work.TrackWorker
class FeedMenuProvider( class FeedMenuProvider(
@ -43,6 +44,11 @@ class FeedMenuProvider(
}.show() }.show()
true true
} }
R.id.action_settings -> {
val intent = SettingsActivity.newTrackerSettingsIntent(context)
context.startActivity(intent)
true
}
else -> false else -> false
} }
} }

@ -35,7 +35,7 @@ class ScreenOrientationHelper(private val activity: Activity) {
isLandscape = !isLandscape isLandscape = !isLandscape
} }
fun observeAutoOrientation() = callbackFlow<Boolean> { fun observeAutoOrientation() = callbackFlow {
val observer = object : ContentObserver(Handler(activity.mainLooper)) { val observer = object : ContentObserver(Handler(activity.mainLooper)) {
override fun onChange(selfChange: Boolean) { override fun onChange(selfChange: Boolean) {
trySendBlocking(isAutoRotationEnabled) trySendBlocking(isAutoRotationEnabled)

@ -1,20 +0,0 @@
package org.koitharu.kotatsu.utils.ext
import android.view.View
import androidx.core.graphics.Insets
fun Insets.getStart(view: View): Int {
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
right
} else {
left
}
}
fun Insets.getEnd(view: View): Int {
return if (view.layoutDirection == View.LAYOUT_DIRECTION_RTL) {
left
} else {
right
}
}

@ -4,11 +4,10 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import kotlinx.coroutines.Deferred
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.utils.BufferedObserver import org.koitharu.kotatsu.utils.BufferedObserver
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
fun <T> LiveData<T?>.observeNotNull(owner: LifecycleOwner, observer: Observer<T>) { fun <T> LiveData<T?>.observeNotNull(owner: LifecycleOwner, observer: Observer<T>) {
this.observe(owner) { this.observe(owner) {

@ -5,8 +5,6 @@ import android.graphics.Rect
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.annotation.StringRes
import androidx.appcompat.widget.TooltipCompat
import androidx.core.view.children import androidx.core.view.children
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView

@ -7,6 +7,9 @@ import android.widget.RemoteViewsService
import coil.ImageLoader import coil.ImageLoader
import coil.executeBlocking import coil.executeBlocking
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Scale
import coil.size.Size
import coil.transform.RoundedCornersTransformation
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.domain.MangaIntent
@ -22,9 +25,15 @@ class RecentListFactory(
) : RemoteViewsService.RemoteViewsFactory { ) : RemoteViewsService.RemoteViewsFactory {
private val dataSet = ArrayList<Manga>() private val dataSet = ArrayList<Manga>()
private val transformation = RoundedCornersTransformation(
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner)
)
private val coverSize = Size(
context.resources.getDimensionPixelSize(R.dimen.widget_cover_width),
context.resources.getDimensionPixelSize(R.dimen.widget_cover_height),
)
override fun onCreate() { override fun onCreate() = Unit
}
override fun getLoadingView() = null override fun getLoadingView() = null
@ -45,6 +54,9 @@ class RecentListFactory(
val cover = coil.executeBlocking( val cover = coil.executeBlocking(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(item.coverUrl) .data(item.coverUrl)
.size(coverSize)
.scale(Scale.FILL)
.transformations(transformation)
.build() .build()
).requireBitmap() ).requireBitmap()
views.setImageViewBitmap(R.id.imageView_cover, cover) views.setImageViewBitmap(R.id.imageView_cover, cover)
@ -61,6 +73,5 @@ class RecentListFactory(
override fun getViewTypeCount() = 1 override fun getViewTypeCount() = 1
override fun onDestroy() { override fun onDestroy() = Unit
}
} }

@ -4,15 +4,12 @@ import android.app.Activity
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -26,7 +23,7 @@ import org.koitharu.kotatsu.widget.shelf.model.CategoryItem
import com.google.android.material.R as materialR import com.google.android.material.R as materialR
class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(), class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(),
OnListItemClickListener<CategoryItem> { OnListItemClickListener<CategoryItem>, View.OnClickListener {
private val viewModel by viewModel<ShelfConfigViewModel>() private val viewModel by viewModel<ShelfConfigViewModel>()
@ -41,10 +38,9 @@ class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(),
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material) setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
} }
adapter = CategorySelectAdapter(this) adapter = CategorySelectAdapter(this)
binding.recyclerView.addItemDecoration(
MaterialDividerItemDecoration(this, RecyclerView.VERTICAL)
)
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
binding.buttonDone.isVisible = true
binding.buttonDone.setOnClickListener(this)
binding.fabAdd.hide() binding.fabAdd.hide()
val appWidgetId = intent?.getIntExtra( val appWidgetId = intent?.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.EXTRA_APPWIDGET_ID,
@ -61,13 +57,9 @@ class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(),
viewModel.onError.observe(this, this::onError) viewModel.onError.observe(this, this::onError)
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onClick(v: View) {
menuInflater.inflate(R.menu.opt_config, menu) when (v.id) {
return super.onCreateOptionsMenu(menu) R.id.button_done -> {
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.action_done -> {
config.categoryId = viewModel.checkedId config.categoryId = viewModel.checkedId
updateWidget() updateWidget()
setResult( setResult(
@ -75,9 +67,8 @@ class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(),
Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId) Intent().putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, config.widgetId)
) )
finish() finish()
true
} }
else -> super.onOptionsItemSelected(item) }
} }
override fun onItemClick(item: CategoryItem, view: View) { override fun onItemClick(item: CategoryItem, view: View) {

@ -7,6 +7,9 @@ import android.widget.RemoteViewsService
import coil.ImageLoader import coil.ImageLoader
import coil.executeBlocking import coil.executeBlocking
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Scale
import coil.size.Size
import coil.transform.RoundedCornersTransformation
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent import org.koitharu.kotatsu.base.domain.MangaIntent
@ -20,14 +23,20 @@ class ShelfListFactory(
private val context: Context, private val context: Context,
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
private val coil: ImageLoader, private val coil: ImageLoader,
widgetId: Int widgetId: Int,
) : RemoteViewsService.RemoteViewsFactory { ) : RemoteViewsService.RemoteViewsFactory {
private val dataSet = ArrayList<Manga>() private val dataSet = ArrayList<Manga>()
private val config = AppWidgetConfig(context, widgetId) private val config = AppWidgetConfig(context, widgetId)
private val transformation = RoundedCornersTransformation(
context.resources.getDimension(R.dimen.appwidget_corner_radius_inner)
)
private val coverSize = Size(
context.resources.getDimensionPixelSize(R.dimen.widget_cover_width),
context.resources.getDimensionPixelSize(R.dimen.widget_cover_height),
)
override fun onCreate() { override fun onCreate() = Unit
}
override fun getLoadingView() = null override fun getLoadingView() = null
@ -56,6 +65,9 @@ class ShelfListFactory(
val cover = coil.executeBlocking( val cover = coil.executeBlocking(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(item.coverUrl) .data(item.coverUrl)
.size(coverSize)
.scale(Scale.FILL)
.transformations(transformation)
.build() .build()
).requireBitmap() ).requireBitmap()
views.setImageViewBitmap(R.id.imageView_cover, cover) views.setImageViewBitmap(R.id.imageView_cover, cover)

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorPrimaryContainer" android:state_checked="true" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="?colorPrimaryContainer" android:state_checked="true" android:state_enabled="false" />
<item android:color="?colorSurfaceVariant" android:state_checked="false" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="?colorSurfaceVariant" android:state_checked="false" android:state_enabled="false" />
</selector>

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?colorPrimary" android:state_checked="true" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="?colorPrimary" android:state_checked="true" android:state_enabled="false" />
<item android:color="?colorOnSurfaceVariant" android:state_checked="false" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="?colorOnSurfaceVariant" android:state_checked="false" android:state_enabled="false" />
</selector>

@ -2,6 +2,6 @@
<shape <shape
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle"> android:shape="rectangle">
<corners android:radius="32dp" /> <corners android:radius="@dimen/appwidget_corner_radius_inner" />
<solid android:color="?attr/colorTertiary" /> <solid android:color="?android:panelColorBackground" />
</shape> </shape>

@ -1,7 +1,11 @@
<rotate <vector
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/abc_ic_arrow_drop_right_black_24dp" android:width="24dp"
android:fromDegrees="-90" android:height="24dp"
android:pivotX="50%" android:tint="?attr/colorControlNormal"
android:pivotY="50%" android:viewportWidth="24"
android:toDegrees="-90" /> android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M7,14l5,-5 5,5z" />
</vector>

@ -1,7 +1,11 @@
<rotate <vector
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/abc_ic_arrow_drop_right_black_24dp" android:width="24dp"
android:fromDegrees="90" android:height="24dp"
android:pivotX="50%" android:tint="?attr/colorControlNormal"
android:pivotY="50%" android:viewportWidth="24"
android:toDegrees="90" /> android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M7,10l5,5 5,-5z" />
</vector>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M2 16h8v-2H2m16 0v-4h-2v4h-4v2h4v4h2v-4h4v-2m-8-8H2v2h12m0 2H2v2h12v-2z" />
</vector>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M14,19H18V5H14M6,19H10V5H6V19Z" />
</vector>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000"
android:pathData="M8,5.14V19.14L19,12.14L8,5.14Z" />
</vector>

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="4dp"
android:left="4dp"
android:right="4dp"
android:top="4dp">
<shape android:shape="oval">
<solid android:color="#000000" />
<size
android:width="20dp"
android:height="20dp" />
</shape>
</item>
</layer-list>

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list
xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#000000" />
<corners android:radius="56dp" />
<size
android:width="64dp"
android:height="28dp" />
</shape>
</item>
</layer-list>

@ -8,9 +8,9 @@
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar" android:id="@+id/appbar"
android:fitsSystemWindows="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout <com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout" android:id="@+id/collapsingToolbarLayout"
@ -23,7 +23,19 @@
android:id="@id/toolbar" android:id="@id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" /> app:layout_collapseMode="pin">
<Button
android:id="@+id/button_done"
style="@style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:text="@string/done"
android:visibility="gone" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>

@ -10,7 +10,18 @@
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" /> android:layout_height="?attr/actionBarSize">
<Button
android:id="@+id/button_done"
style="@style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:text="@string/save" />
</com.google.android.material.appbar.MaterialToolbar>
<ScrollView <ScrollView
android:id="@+id/scrollView" android:id="@+id/scrollView"
@ -57,7 +68,7 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_tracker" android:id="@+id/switch_tracker"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -62,6 +62,19 @@
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_biometric"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:checked="true"
android:text="@string/use_fingerprint"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/layout_password"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_cancel" android:id="@+id/button_cancel"
style="@style/Widget.Material3.Button.OutlinedButton" style="@style/Widget.Material3.Button.OutlinedButton"

@ -12,8 +12,18 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:menu="@menu/opt_config" app:title="@string/add_to_favourites">
app:title="@string/add_to_favourites" />
<Button
android:id="@+id/button_done"
style="@style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginHorizontal="@dimen/toolbar_button_margin"
android:text="@string/done" />
</com.google.android.material.appbar.MaterialToolbar>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView_categories" android:id="@+id/recyclerView_categories"

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="14dp"
android:paddingTop="8dp"
android:paddingEnd="14dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/inputLayout"
app:boxBackgroundMode="filled"
app:boxBackgroundColor="@android:color/transparent"
app:hintEnabled="false"
app:expandedHintEnabled="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputEdit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:singleLine="true"
tools:hint="@tools:sample/lorem[2]" />
</com.google.android.material.textfield.TextInputLayout>
</FrameLayout>

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout <ScrollView
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
@ -67,4 +71,5 @@
tools:value="100" tools:value="100"
tools:visibility="visible" /> tools:visibility="visible" />
</LinearLayout> </LinearLayout>
</ScrollView>

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckedTextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/checkedTextView"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:background="?android:selectableItemBackground"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
android:gravity="start|center_vertical"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:checked="true"
tools:text="@tools:sample/lorem[4]" />

@ -4,10 +4,11 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/chapter_list_item_height" android:layout_height="wrap_content"
android:background="?selectableItemBackground" android:background="?selectableItemBackground"
android:baselineAligned="false" android:baselineAligned="false"
android:gravity="center_vertical" android:gravity="center_vertical"
android:minHeight="@dimen/chapter_list_item_height"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
@ -44,9 +45,9 @@
android:id="@+id/textView_description" android:id="@+id/textView_description"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
android:layout_marginTop="2dp"
android:textAppearance="?attr/textAppearanceBodySmall" android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="05.10.2021 • Scanlator" /> tools:text="05.10.2021 • Scanlator" />
</LinearLayout> </LinearLayout>

@ -18,8 +18,8 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:src="@tools:sample/backgrounds/scenic" app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"/> tools:src="@tools:sample/backgrounds/scenic" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -53,8 +53,9 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:baselineAligned="false"
android:gravity="center"> android:clipChildren="false"
android:orientation="horizontal">
<TextView <TextView
android:id="@+id/textView_tags" android:id="@+id/textView_tags"
@ -72,9 +73,12 @@
android:id="@+id/textView_rating" android:id="@+id/textView_rating"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:drawablePadding="4dp" android:drawablePadding="4dp"
android:elegantTextHeight="false"
android:gravity="center_vertical"
android:paddingStart="6dp" android:paddingStart="6dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
app:drawableEndCompat="@drawable/ic_star" app:drawableEndCompat="@drawable/ic_star"
tools:ignore="RtlSymmetry" tools:ignore="RtlSymmetry"
tools:text="9.6" /> tools:text="9.6" />

@ -3,7 +3,7 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/imageView_cover" android:id="@+id/imageView_cover"
android:layout_width="92dp" android:layout_width="@dimen/widget_cover_width"
android:layout_height="128dp" android:layout_height="@dimen/widget_cover_height"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />

@ -9,7 +9,7 @@
android:paddingStart="?listPreferredItemPaddingStart" android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"> android:paddingEnd="?listPreferredItemPaddingEnd">
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_local" android:id="@+id/switch_local"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -3,21 +3,24 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:padding="4dp"
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
<LinearLayout <LinearLayout
android:id="@+id/rootLayout" android:id="@+id/rootLayout"
android:layout_width="92dp" android:layout_width="@dimen/widget_cover_width"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:background="@drawable/bg_appwidget_card"
android:foreground="?android:selectableItemBackground"
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="4dp" tools:ignore="UnusedAttribute,UselessParent">
tools:ignore="UselessParent">
<ImageView <ImageView
android:id="@+id/imageView_cover" android:id="@+id/imageView_cover"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="128dp" android:layout_height="@dimen/widget_cover_height"
android:scaleType="centerCrop" android:scaleType="centerCrop"
tools:ignore="ContentDescription" /> tools:ignore="ContentDescription" />
@ -25,12 +28,12 @@
android:id="@+id/textView_title" android:id="@+id/textView_title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elegantTextHeight="false"
android:ellipsize="end" android:ellipsize="end"
android:gravity="center"
android:lines="2" android:lines="2"
android:shadowColor="@android:color/black" android:paddingHorizontal="4dp"
android:shadowRadius="1" android:paddingBottom="4dp"
android:textColor="@android:color/white" /> android:textColor="?android:attr/textColorPrimary" />
</LinearLayout> </LinearLayout>

@ -48,7 +48,7 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_toggle" android:id="@+id/switch_toggle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

@ -47,7 +47,7 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_toggle" android:id="@+id/switch_toggle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.materialswitch.MaterialSwitch
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false" />

@ -2,14 +2,17 @@
<FrameLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:padding="4dp"
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
<StackView <StackView
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/stackView" android:id="@+id/stackView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:listitem="@layout/item_shelf" /> tools:listitem="@layout/item_recent" />
<TextView <TextView
android:id="@+id/textView_holder" android:id="@+id/textView_holder"

@ -2,7 +2,10 @@
<FrameLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:padding="4dp"
android:theme="@style/Theme.Kotatsu.AppWidgetContainer">
<GridView <GridView
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
@ -21,9 +24,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:shadowColor="@android:color/black"
android:shadowRadius="1"
android:text="@string/you_have_not_favourites_yet" android:text="@string/you_have_not_favourites_yet"
android:textColor="@android:color/white" /> android:textColor="?android:attr/textColorPrimary" />
</FrameLayout> </FrameLayout>

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_done"
android:orderInCategory="0"
android:title="@string/done"
app:showAsAction="always|withText" />
</menu>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_share"
android:icon="@android:drawable/ic_menu_share"
android:showAsAction="ifRoom"
android:title="@string/share"
tools:ignore="AppCompatResource" />
</menu>

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:icon="@drawable/ic_pause"
android:id="@+id/action_pause"
android:title="Pause"
app:showAsAction="ifRoom|withText" />
<item
android:icon="@drawable/ic_resume"
android:id="@+id/action_resume"
android:title="Resume"
app:showAsAction="ifRoom|withText" />
</menu>

@ -15,4 +15,10 @@
android:title="@string/clear_feed" android:title="@string/clear_feed"
app:showAsAction="never" /> app:showAsAction="never" />
<item
android:id="@+id/action_settings"
android:orderInCategory="50"
android:title="@string/settings"
app:showAsAction="never" />
</menu> </menu>

@ -10,4 +10,9 @@
app:actionViewClass="androidx.appcompat.widget.SearchView" app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" /> app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/action_disable_all"
android:title="@string/disable_all"
app:showAsAction="never" />
</menu> </menu>

@ -298,4 +298,8 @@
<string name="disable_battery_optimization_summary">Hilft bei der Überprüfung von Hintergrundaktualisierungen</string> <string name="disable_battery_optimization_summary">Hilft bei der Überprüfung von Hintergrundaktualisierungen</string>
<string name="send">Senden</string> <string name="send">Senden</string>
<string name="crash_text">Etwas ist schief gelaufen. Bitte senden Sie einen Fehlerbericht an die Entwickler, damit wir das Problem beheben können.</string> <string name="crash_text">Etwas ist schief gelaufen. Bitte senden Sie einen Fehlerbericht an die Entwickler, damit wir das Problem beheben können.</string>
<string name="disable_all">Alle deaktivieren</string>
<string name="use_fingerprint">Fingerabdruck verwenden, falls vorhanden</string>
<string name="appwidget_shelf_description">Manga aus Ihren Favoriten</string>
<string name="appwidget_recent_description">Ihr kürzlich gelesener Manga</string>
</resources> </resources>

@ -270,7 +270,7 @@
<string name="text_delete_local_manga_batch">¿Eliminar elementos seleccionados del dispositivo de forma permanente\?</string> <string name="text_delete_local_manga_batch">¿Eliminar elementos seleccionados del dispositivo de forma permanente\?</string>
<string name="hide">Ocultar</string> <string name="hide">Ocultar</string>
<string name="download_slowdown">Ralentización de la descarga</string> <string name="download_slowdown">Ralentización de la descarga</string>
<string name="new_sources_text">Nuevas fuentes de manga disponibles</string> <string name="new_sources_text">Nuevas fuentes de manga son disponibles</string>
<string name="parallel_downloads">Descargas en paralelo</string> <string name="parallel_downloads">Descargas en paralelo</string>
<string name="download_slowdown_summary">Ayuda a evitar el bloqueo de tu dirección IP</string> <string name="download_slowdown_summary">Ayuda a evitar el bloqueo de tu dirección IP</string>
<string name="local_manga_processing">Procesamiento de manga guardado</string> <string name="local_manga_processing">Procesamiento de manga guardado</string>
@ -294,4 +294,11 @@
<string name="name">Nombre</string> <string name="name">Nombre</string>
<string name="edit">Editar</string> <string name="edit">Editar</string>
<string name="edit_category">Editar categoría</string> <string name="edit_category">Editar categoría</string>
<string name="disable_all">Desactivar todo</string>
<string name="disable_battery_optimization">Desactivar la optimización de la batería</string>
<string name="send">Enviar</string>
<string name="crash_text">Algo ha ido mal. Por favor, envía un informe de errores a los desarrolladores para ayudarnos a solucionarlo.</string>
<string name="use_fingerprint">Utilizar la huella dactilar si está disponible</string>
<string name="appwidget_shelf_description">Mangas de tus favoritos</string>
<string name="appwidget_recent_description">Sus mangas recientemente leídos</string>
</resources> </resources>

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="hours_ago">
<item quantity="one">%1$d tunti sitten</item>
<item quantity="other">%1$d tuntia sitten</item>
</plurals>
<plurals name="chapters">
<item quantity="one">%1$d luku</item>
<item quantity="other">%1$d lukua</item>
</plurals>
<plurals name="minutes_ago">
<item quantity="one">%1$d minuutti sitten</item>
<item quantity="other">%1$d minuuttia sitten</item>
</plurals>
<plurals name="items">
<item quantity="one">%1$d kohde</item>
<item quantity="other">%1$d kohdetta</item>
</plurals>
<plurals name="new_chapters">
<item quantity="one">%1$d uusi luku</item>
<item quantity="other">%1$d uutta lukua</item>
</plurals>
<plurals name="days_ago">
<item quantity="one">%1$d päivä sitten</item>
<item quantity="other">%1$d päivää sitten</item>
</plurals>
</resources>

@ -297,4 +297,9 @@
<string name="disable_battery_optimization">Poista akun optimoinnin käytöstä</string> <string name="disable_battery_optimization">Poista akun optimoinnin käytöstä</string>
<string name="disable_battery_optimization_summary">Auttaa taustapäivitystarkastuksissa</string> <string name="disable_battery_optimization_summary">Auttaa taustapäivitystarkastuksissa</string>
<string name="crash_text">Jokin meni pieleen. Lähetä vikailmoitus kehittäjille, jotta voimme korjata sen.</string> <string name="crash_text">Jokin meni pieleen. Lähetä vikailmoitus kehittäjille, jotta voimme korjata sen.</string>
<string name="disable_all">Poista kaikki käytöstä</string>
<string name="send">Lähetä</string>
<string name="use_fingerprint">Käytä sormenjälkeä, jos käytettävissä</string>
<string name="appwidget_shelf_description">Manga suosikeistasi</string>
<string name="appwidget_recent_description">Äskettäin lukemasi manga</string>
</resources> </resources>

@ -298,4 +298,8 @@
<string name="disable_battery_optimization_summary">Aide à la vérification des mises à jour des antécédents</string> <string name="disable_battery_optimization_summary">Aide à la vérification des mises à jour des antécédents</string>
<string name="crash_text">Un problème est survenu. Veuillez soumettre un rapport de bogue aux développeurs pour nous aider à le corriger.</string> <string name="crash_text">Un problème est survenu. Veuillez soumettre un rapport de bogue aux développeurs pour nous aider à le corriger.</string>
<string name="send">Envoyer</string> <string name="send">Envoyer</string>
<string name="disable_all">Tout désactiver</string>
<string name="use_fingerprint">Utiliser l\'empreinte digitale si elle est disponible</string>
<string name="appwidget_recent_description">Vos mangas récemment lus</string>
<string name="appwidget_shelf_description">Les mangas de vos favoris</string>
</resources> </resources>

@ -298,4 +298,8 @@
<string name="disable_battery_optimization_summary">Contribuisce ai controlli di aggiornamento in sfondo</string> <string name="disable_battery_optimization_summary">Contribuisce ai controlli di aggiornamento in sfondo</string>
<string name="crash_text">Qualcosa è andato storto. Si prega di inviare una segnalazione di bug agli sviluppatori per aiutarci a risolvere il problema.</string> <string name="crash_text">Qualcosa è andato storto. Si prega di inviare una segnalazione di bug agli sviluppatori per aiutarci a risolvere il problema.</string>
<string name="send">Invia</string> <string name="send">Invia</string>
<string name="disable_all">Disabilita tutto</string>
<string name="use_fingerprint">Usa le impronte digitali se disponibili</string>
<string name="appwidget_shelf_description">Manga dai preferiti</string>
<string name="appwidget_recent_description">I manga letti di recente</string>
</resources> </resources>

@ -274,7 +274,7 @@
<string name="parallel_downloads">並列ダウンロード</string> <string name="parallel_downloads">並列ダウンロード</string>
<string name="chapters_will_removed_background">チャプターはバックグラウンドで削除されます。時間がかかる場合があります</string> <string name="chapters_will_removed_background">チャプターはバックグラウンドで削除されます。時間がかかる場合があります</string>
<string name="hide">隠す</string> <string name="hide">隠す</string>
<string name="new_sources_text">新しいマンガソースが利用可能になりました</string> <string name="new_sources_text">新しいマンガソースが利用可能です</string>
<string name="check_new_chapters_title">新着チャプターの確認とお知らせ</string> <string name="check_new_chapters_title">新着チャプターの確認とお知らせ</string>
<string name="show_notification_new_chapters_on">読んでいるマンガの更新情報をお知らせします</string> <string name="show_notification_new_chapters_on">読んでいるマンガの更新情報をお知らせします</string>
<string name="notifications_enable">通知を有効にする</string> <string name="notifications_enable">通知を有効にする</string>
@ -298,4 +298,8 @@
<string name="disable_battery_optimization_summary">バックグラウンドの更新チェックを支援</string> <string name="disable_battery_optimization_summary">バックグラウンドの更新チェックを支援</string>
<string name="crash_text">何か問題が発生しました。開発者にバグレポートを提出し、解決にご協力ください。</string> <string name="crash_text">何か問題が発生しました。開発者にバグレポートを提出し、解決にご協力ください。</string>
<string name="send">送信</string> <string name="send">送信</string>
<string name="disable_all">すべて無効にする</string>
<string name="appwidget_recent_description">最近読んだ漫画</string>
<string name="use_fingerprint">指紋がある場合は、指紋を使用する</string>
<string name="appwidget_shelf_description">お気に入りの漫画</string>
</resources> </resources>

@ -260,4 +260,21 @@
<string name="percent_string_pattern">%1$s%%</string> <string name="percent_string_pattern">%1$s%%</string>
<string name="various_languages">Forskjellige språk</string> <string name="various_languages">Forskjellige språk</string>
<string name="chapters_empty">Ingen kapitler i denne mangaen</string> <string name="chapters_empty">Ingen kapitler i denne mangaen</string>
<string name="content">Innhold</string>
<string name="appearance">Utseende</string>
<string name="name">Navn</string>
<string name="edit">Rediger</string>
<string name="edit_category">Rediger kategori</string>
<string name="dns_over_https">DNS over HTTPS</string>
<string name="default_mode">Forvalgt modus</string>
<string name="removal_completed">Fjerning fullført</string>
<string name="empty_favourite_categories">Ingen favorittkategorier</string>
<string name="bookmark_add">Legg til bokmerke</string>
<string name="bookmarks">Bokmerker</string>
<string name="bookmark_removed">Bokmerke fjernet</string>
<string name="bookmark_added">Bokmerke lagt til</string>
<string name="undo">Angre</string>
<string name="removed_from_history">Fjernet fra historikk</string>
<string name="bookmark_remove">Fjern bokmerke</string>
<string name="send">Send</string>
</resources> </resources>

@ -2,5 +2,4 @@
<resources> <resources>
<bool name="light_status_bar">false</bool> <bool name="light_status_bar">false</bool>
<bool name="light_navigation_bar">false</bool> <bool name="light_navigation_bar">false</bool>
<bool name="elevation_overlay_enabled">true</bool>
</resources> </resources>

@ -282,4 +282,23 @@
<string name="edit">Изменить</string> <string name="edit">Изменить</string>
<string name="edit_category">Изменить категорию</string> <string name="edit_category">Изменить категорию</string>
<string name="empty_favourite_categories">Нет категорий избранного</string> <string name="empty_favourite_categories">Нет категорий избранного</string>
<string name="bookmark_add">Добавить закладку</string>
<string name="bookmark_remove">Удалить закладку</string>
<string name="bookmarks">Закладки</string>
<string name="bookmark_removed">Закладка удалена</string>
<string name="bookmark_added">Закладка добавлена</string>
<string name="undo">Отменить</string>
<string name="removed_from_history">Удалено из истории</string>
<string name="dns_over_https">DNS через HTTPS</string>
<string name="default_mode">Режим по умолчанию</string>
<string name="detect_reader_mode">Автоопределение режима чтения</string>
<string name="detect_reader_mode_summary">Автоматически определяет, является ли манга веб-комиксом</string>
<string name="disable_battery_optimization">Отключить оптимизацию батареи</string>
<string name="disable_battery_optimization_summary">Помогает с фоновой проверкой обновлений</string>
<string name="crash_text">Что-то пошло не так. Пожалуйста, отправьте отчёт разработчикам, чтобы помочь всё исправить</string>
<string name="send">Отправить</string>
<string name="disable_all">Отключить все</string>
<string name="use_fingerprint">Использовать отпечаток пальца, если доступно</string>
<string name="appwidget_shelf_description">Манга из Вашего избранного</string>
<string name="appwidget_recent_description">Манга, которую Вы недавно читали</string>
</resources> </resources>

@ -194,7 +194,6 @@
<string name="search_results">Arama sonuçları</string> <string name="search_results">Arama sonuçları</string>
<string name="waiting_for_network">Ağ bekleniyor…</string> <string name="waiting_for_network">Ağ bekleniyor…</string>
<string name="repeat_password">Parolayı tekrarla</string> <string name="repeat_password">Parolayı tekrarla</string>
<string name="prefer_rtl_reader">Sağdan sola (←) okuyucuyu tercih et</string>
<string name="dont_check">Denetleme</string> <string name="dont_check">Denetleme</string>
<string name="wrong_password">Yanlış parola</string> <string name="wrong_password">Yanlış parola</string>
<string name="report_github">GitHub\'da sorun oluştur</string> <string name="report_github">GitHub\'da sorun oluştur</string>
@ -299,4 +298,8 @@
<string name="disable_battery_optimization_summary">Arka planda güncelleme denetimlerine yardımcı olur</string> <string name="disable_battery_optimization_summary">Arka planda güncelleme denetimlerine yardımcı olur</string>
<string name="crash_text">Bir şeyler yanlış gitti. Düzeltmemize yardımcı olması için lütfen geliştiricilere bir hata bildirimi gönderin.</string> <string name="crash_text">Bir şeyler yanlış gitti. Düzeltmemize yardımcı olması için lütfen geliştiricilere bir hata bildirimi gönderin.</string>
<string name="send">Gönder</string> <string name="send">Gönder</string>
<string name="disable_all">Tümünü devre dışı bırak</string>
<string name="use_fingerprint">Varsa parmak izi kullan</string>
<string name="appwidget_shelf_description">Favorilerinizden mangalar</string>
<string name="appwidget_recent_description">Son okuduğunuz mangalar</string>
</resources> </resources>

@ -106,7 +106,7 @@
<string name="manga_shelf">Полиця</string> <string name="manga_shelf">Полиця</string>
<string name="recent_manga">Недавні</string> <string name="recent_manga">Недавні</string>
<string name="pages_animation">Анімація перегортання</string> <string name="pages_animation">Анімація перегортання</string>
<string name="manga_save_location">Папка для завантажень</string> <string name="manga_save_location">Тека для завантажень</string>
<string name="other_storage">Інше сховище</string> <string name="other_storage">Інше сховище</string>
<string name="done">Готово</string> <string name="done">Готово</string>
<string name="all_favourites">Усі улюблені</string> <string name="all_favourites">Усі улюблені</string>
@ -288,12 +288,18 @@
<string name="bookmarks">Закладки</string> <string name="bookmarks">Закладки</string>
<string name="bookmark_removed">Закладка видалена</string> <string name="bookmark_removed">Закладка видалена</string>
<string name="bookmark_added">Додано закладку</string> <string name="bookmark_added">Додано закладку</string>
<string name="undo">Скасувати</string> <string name="undo">Відмінити</string>
<string name="removed_from_history">Видалено з історії</string> <string name="removed_from_history">Видалено з історії</string>
<string name="dns_over_https">DNS через HTTPS</string> <string name="dns_over_https">DNS через HTTPS</string>
<string name="default_mode">Типовий режим</string> <string name="default_mode">Режим за замовчуванням</string>
<string name="detect_reader_mode_summary">Автоматично визначати, чи є манга вебтуном</string> <string name="detect_reader_mode_summary">Автоматично визначати, чи є манга вебтуном</string>
<string name="detect_reader_mode">Автовизначення режиму читання</string> <string name="detect_reader_mode">Автовизначення режиму читання</string>
<string name="disable_battery_optimization">Вимкнути оптимізацію акумулятора</string> <string name="disable_battery_optimization">Вимкнути оптимізацію акумулятора</string>
<string name="disable_battery_optimization_summary">Допомагає з перевірками фонових оновлень</string> <string name="disable_battery_optimization_summary">Допомагає з перевірками фонових оновлень</string>
<string name="crash_text">Щось пішло не так. Будь ласка, надішліть звіт про помилку розробникам, щоб допомогти нам її виправити.</string>
<string name="send">Надіслати</string>
<string name="disable_all">Вимкнути все</string>
<string name="use_fingerprint">Використовувати відбиток пальця, якщо доступно</string>
<string name="appwidget_shelf_description">Манга з Вашого улюбленого</string>
<string name="appwidget_recent_description">Манга, яку Ви нещодавно читали</string>
</resources> </resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="appwidget_corner_radius_inner">@android:dimen/system_app_widget_inner_radius</dimen>
</resources>

@ -41,4 +41,10 @@
<item name="android:textColorHighlightInverse">@color/m3_dynamic_dark_highlighted_text</item> <item name="android:textColorHighlightInverse">@color/m3_dynamic_dark_highlighted_text</item>
<item name="android:textColorAlertDialogListItem">@color/m3_dynamic_default_color_primary_text</item> <item name="android:textColorAlertDialogListItem">@color/m3_dynamic_default_color_primary_text</item>
</style> </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>
</style>
</resources> </resources>

@ -3,9 +3,296 @@
<string name="settings">设置</string> <string name="settings">设置</string>
<string name="local_storage">本地存储</string> <string name="local_storage">本地存储</string>
<string name="favourites">喜欢</string> <string name="favourites">喜欢</string>
<string name="history">浏览历史</string> <string name="history">历史</string>
<string name="error_occurred">发生了一个错误</string> <string name="error_occurred">发生了一个错误</string>
<string name="network_error">未能连接到互联网</string> <string name="network_error">未能连接到互联网</string>
<string name="chapters">章节</string> <string name="chapters">章节</string>
<string name="list">列表</string> <string name="list">列表</string>
<string name="data_restored_with_errors">数据被恢复了,但有错误</string>
<string name="processing_">正在处理…</string>
<string name="newest">最新</string>
<string name="by_rating">评分</string>
<string name="cookies_cleared">已删除所有 cookie</string>
<string name="data_restored_success">所有数据都被恢复了</string>
<string name="silent">无声</string>
<string name="report_github">在GitHub上创建问题</string>
<string name="preparing_">准备…</string>
<string name="file_not_found">未找到文件</string>
<string name="yesterday">昨日</string>
<string name="backup_information">你可以创建你的历史和收藏的备份并恢复它</string>
<string name="just_now">现在</string>
<string name="long_ago">很久以前</string>
<string name="group"></string>
<string name="tap_to_try_again">轻点以重试</string>
<string name="reader_mode_hint">所选择的配置将因这部漫画而被记住</string>
<string name="captcha_required">需要验证码</string>
<string name="captcha_solve">解决</string>
<string name="today">今天</string>
<string name="clear_cookies">清除cookies</string>
<string name="chapters_checking_progress">检查新的章节: %1$d/%2$d</string>
<string name="error_empty_name">你必须输入一个名称</string>
<string name="new_sources_text">有了新的漫画来源</string>
<string name="suggestions_summary">根据你的喜好推荐漫画</string>
<string name="suggestions_info">所有的数据都在这个设备上进行本地分析. 您的个人数据不会被转移到任何服务机构</string>
<string name="never">从不</string>
<string name="show_notification_new_chapters_on">你会收到你正在阅读的漫画的更新通知</string>
<string name="nsfw">18+</string>
<string name="various_languages">各种语言</string>
<string name="search_chapters">查找章节</string>
<string name="suggestions_excluded_genres">排除流派</string>
<string name="suggestions_updating">建议更新</string>
<string name="check_new_chapters_title">检查新的章节并通知有关情况</string>
<string name="close_menu">关闭菜单</string>
<string name="open_menu">打开菜单</string>
<string name="details">详细内容</string>
<string name="detailed_list">详细列表</string>
<string name="grid">网格</string>
<string name="list_mode">列表模式</string>
<string name="remote_sources">远程资源</string>
<string name="loading_">加载中…</string>
<string name="computing_">计算中…</string>
<string name="chapter_d_of_d">%1$d/%2$d章节</string>
<string name="close">关闭</string>
<string name="try_again">再试一次</string>
<string name="clear_history">清除历史</string>
<string name="nothing_found">没有发现</string>
<string name="history_is_empty">尚无历史</string>
<string name="read">阅读</string>
<string name="you_have_not_favourites_yet">尚无收藏夹</string>
<string name="add_to_favourites">收藏这个</string>
<string name="add_new_category">新类别</string>
<string name="add">添加</string>
<string name="enter_category_name">输入类别名称</string>
<string name="save">保存</string>
<string name="share">分享</string>
<string name="create_shortcut">创建快捷方式…</string>
<string name="share_s">分享%s</string>
<string name="search">搜索</string>
<string name="search_manga">搜索漫画</string>
<string name="manga_downloading_">正在下载…</string>
<string name="download_complete">已下载</string>
<string name="downloads">下载</string>
<string name="by_name">名称</string>
<string name="popular">热门</string>
<string name="updated">更新</string>
<string name="sort_order">排序顺序</string>
<string name="filter">过滤器</string>
<string name="theme">主题</string>
<string name="dark">黑暗</string>
<string name="light">浅色</string>
<string name="automatic">跟随系统</string>
<string name="pages">页数</string>
<string name="clear">清除</string>
<string name="text_clear_history_prompt">永久清除所有阅读历史\?</string>
<string name="remove">删除</string>
<string name="_s_removed_from_history">\"%s\"从历史中删除</string>
<string name="_s_deleted_from_local_storage">\"%s\"从本地存储中删除</string>
<string name="wait_for_loading_finish">等待加载完成…</string>
<string name="save_page">保存页面</string>
<string name="page_saved">保存</string>
<string name="share_image">分享图片</string>
<string name="_import">输入</string>
<string name="delete">删除</string>
<string name="operation_not_supported">不支持此操作</string>
<string name="text_file_not_supported">选择 ZIP 或 CBZ 文件.</string>
<string name="no_description">无描述</string>
<string name="history_and_cache">历史和缓存</string>
<string name="clear_pages_cache">清除页面缓存</string>
<string name="cache">缓存</string>
<string name="text_file_sizes">B|kB|MB|GB|TB</string>
<string name="standard">标准</string>
<string name="webtoon">Webtoon</string>
<string name="read_mode">阅读模式</string>
<string name="grid_size">网格大小</string>
<string name="search_on_s">在%s上搜索</string>
<string name="delete_manga">删除漫画</string>
<string name="text_delete_local_manga">从设备中永久删除\"%s\"\?</string>
<string name="reader_settings">阅读器设置</string>
<string name="switch_pages">切换页面</string>
<string name="volume_buttons">音量按钮</string>
<string name="_continue">继续</string>
<string name="warning">警告</string>
<string name="network_consumption_warning">这可能会传输大量的数据</string>
<string name="dont_ask_again">不要再问了</string>
<string name="cancelling_">取消中…</string>
<string name="taps_on_edges">边缘点击</string>
<string name="error">错误</string>
<string name="clear_thumbs_cache">清除缩略图缓存</string>
<string name="clear_search_history">清除搜索历史</string>
<string name="search_history_cleared">清除</string>
<string name="gestures_only">仅限手势</string>
<string name="internal_storage">内部存储</string>
<string name="external_storage">外部存储</string>
<string name="domain">范围</string>
<string name="application_update">检查应用程序新版本</string>
<string name="app_update_available">新版本应用程序已经推出</string>
<string name="show_notification_app_update">有新版本可用时显示通知</string>
<string name="open_in_browser">在网络浏览器中打开</string>
<string name="large_manga_save_confirm">这部漫画有%s.全部保存\?</string>
<string name="save_manga">保存</string>
<string name="notifications">通知</string>
<string name="new_chapters">新章节</string>
<string name="download">下载</string>
<string name="read_from_start">从头开始阅读</string>
<string name="restart">重新启动</string>
<string name="notifications_settings">通知设置</string>
<string name="notification_sound">通知声音</string>
<string name="light_indicator">LED指示器</string>
<string name="vibration">振动</string>
<string name="favourites_categories">收藏夹类别</string>
<string name="categories_">类别…</string>
<string name="rename">重命名</string>
<string name="category_delete_confirm">将\"%s\"类别从你的收藏夹中删除?
\n里面的所有漫画都会丢失。</string>
<string name="remove_category">删除</string>
<string name="text_empty_holder_primary">这里有点空…</string>
<string name="text_categories_holder">您可以使用类别来组织您的收藏夹。按«+»创建一个类别</string>
<string name="text_search_holder_secondary">尝试重新表述查询。</string>
<string name="text_history_holder_primary">你所读的内容将在这里显示</string>
<string name="text_history_holder_secondary">在侧面菜单中找到要读的内容.</string>
<string name="text_local_holder_primary">先保存内容</string>
<string name="text_local_holder_secondary">从在线来源保存或导入文件.</string>
<string name="manga_shelf">架子</string>
<string name="recent_manga">最近</string>
<string name="pages_animation">页面动画</string>
<string name="manga_save_location">下载文件夹</string>
<string name="not_available">不详</string>
<string name="cannot_find_available_storage">没有可用的存储空间</string>
<string name="other_storage">其他存储</string>
<string name="done">完成</string>
<string name="all_favourites">所有收藏夹</string>
<string name="favourites_category_empty">空类别</string>
<string name="read_later">稍后阅读</string>
<string name="updates">更新</string>
<string name="text_feed_holder">你正在阅读的新章节显示在这里</string>
<string name="search_results">搜索结果</string>
<string name="related">相关</string>
<string name="new_version_s">新版本: %s</string>
<string name="waiting_for_network">等待网络…</string>
<string name="clear_updates_feed">清除更新源</string>
<string name="updates_feed_cleared">已清除</string>
<string name="rotate_screen">旋转屏幕</string>
<string name="update">更新</string>
<string name="feed_will_update_soon">源更新即将开始</string>
<string name="track_sources">查找更新</string>
<string name="dont_check">不要检查</string>
<string name="enter_password">输入密码</string>
<string name="wrong_password">密码错误</string>
<string name="protect_application">保护应用程序</string>
<string name="protect_application_summary">在启动Kotatsu时要求输入密码</string>
<string name="repeat_password">重复密码</string>
<string name="passwords_mismatch">密码不匹配</string>
<string name="about">关于</string>
<string name="app_version">版本%s</string>
<string name="check_for_updates">检查更新</string>
<string name="checking_for_updates">检查更新…</string>
<string name="update_check_failed">无法查找更新</string>
<string name="no_update_available">没有更新</string>
<string name="right_to_left">从右到左 (←)</string>
<string name="create_category">新类别</string>
<string name="scale_mode">缩放模式</string>
<string name="zoom_mode_fit_center">适应中心</string>
<string name="zoom_mode_fit_height">适应高度</string>
<string name="zoom_mode_fit_width">适应宽度</string>
<string name="zoom_mode_keep_start">从头开始</string>
<string name="black_dark_theme">黑色</string>
<string name="black_dark_theme_summary">在AMOLED屏幕上使用更少电池</string>
<string name="backup_restore">备份和还原</string>
<string name="create_backup">创建数据备份</string>
<string name="restore_backup">从备份中恢复</string>
<string name="data_restored">恢复</string>
<string name="clear_feed">清除文件</string>
<string name="text_clear_updates_feed_prompt">永久地清除所有的更新历史?</string>
<string name="check_for_new_chapters">检查新的章节</string>
<string name="reverse">撤销</string>
<string name="sign_in">登录</string>
<string name="auth_required">登录后可查看此内容</string>
<string name="default_s">默认值: %s</string>
<string name="_and_x_more">...和%1$d更多</string>
<string name="next">下一页</string>
<string name="protect_application_subtitle">输入密码以启动应用程序</string>
<string name="confirm">确认</string>
<string name="password_length_hint">密码必须是4个字符或以上</string>
<string name="search_only_on_s">仅在%s上搜索</string>
<string name="text_clear_search_history_prompt">永久地删除所有最近的搜索查询?</string>
<string name="other">其他</string>
<string name="welcome">欢迎</string>
<string name="backup_saved">保存备份</string>
<string name="tracker_warning">一些设备有不同的系统行为, 这可能会破坏后台任务.</string>
<string name="read_more">阅读更多</string>
<string name="queued">排队</string>
<string name="text_downloads_holder">没有有效的下载</string>
<string name="chapter_is_missing_text">下载或在线阅读这缺失的章节.</string>
<string name="chapter_is_missing">该章缺失</string>
<string name="about_app_translation_summary">翻译此应用程序</string>
<string name="about_app_translation">翻译</string>
<string name="about_feedback">反馈信息</string>
<string name="about_feedback_4pda">关于4PDA主题</string>
<string name="auth_complete">授权</string>
<string name="auth_not_supported_by">不支持在%s上登录</string>
<string name="text_clear_cookies_prompt">你将被从所有来源中注销</string>
<string name="genres">类型</string>
<string name="state_ongoing">连载中</string>
<string name="state_finished">已完结</string>
<string name="date_format">日期格式</string>
<string name="system_default">默认</string>
<string name="exclude_nsfw_from_history">将NSFW漫画排除在历史之外</string>
<string name="show_pages_numbers">页数</string>
<string name="enabled_sources">使用图源</string>
<string name="available_sources">现有图源</string>
<string name="dynamic_theme">动态主题</string>
<string name="dynamic_theme_summary">应用根据你的墙纸的颜色方案创建的主题</string>
<string name="importing_progress">导入漫画: %1$d of %2$d</string>
<string name="screenshots_policy">屏幕截图</string>
<string name="screenshots_allow">允许</string>
<string name="screenshots_block_nsfw">禁止18+</string>
<string name="screenshots_block_all">始终阻止</string>
<string name="suggestions">建议</string>
<string name="suggestions_enable">启用建议</string>
<string name="text_suggestion_holder">开始阅读漫画,你会得到个性化的建议</string>
<string name="exclude_nsfw_from_suggestions">请勿推荐18+漫画</string>
<string name="enabled">启用</string>
<string name="disabled">禁用</string>
<string name="filter_load_error">无法加载流派列表</string>
<string name="reset_filter">重置过滤器</string>
<string name="find_genre">查找流派</string>
<string name="onboard_text">选择你想看的漫画的语言. 你可以在以后的设置中改变它.</string>
<string name="only_using_wifi">只在Wi-Fi上使用</string>
<string name="always">总是</string>
<string name="preload_pages">预加载页面</string>
<string name="logged_in_as">以%s身份登录</string>
<string name="chapters_empty">这部漫画中没有章节</string>
<string name="appearance">外观</string>
<string name="content">内容</string>
<string name="suggestions_excluded_genres_summary">指定您不希望在建议中看到的类型</string>
<string name="text_delete_local_manga_batch">从设备中永久删除所选项目\?</string>
<string name="removal_completed">删除已完成</string>
<string name="parallel_downloads">并行下载</string>
<string name="download_slowdown">下载速度减慢</string>
<string name="batch_manga_save_confirm">你确定你要下载所有选定的漫画及其所有章节吗\? 这个动作会消耗大量的流量和存储空间</string>
<string name="download_slowdown_summary">有助于避免阻断你的IP地址</string>
<string name="local_manga_processing">保存的漫画处理</string>
<string name="chapters_will_removed_background">章节将在后台被删除. 这可能需要一些时间</string>
<string name="hide">隐藏</string>
<string name="show_notification_new_chapters_off">你将不会收到通知但新的章节将在列表中突出显示</string>
<string name="notifications_enable">启用通知</string>
<string name="name">命名</string>
<string name="edit">编辑</string>
<string name="edit_category">编辑类别</string>
<string name="empty_favourite_categories">没有收藏夹类别</string>
<string name="bookmark_add">添加书签</string>
<string name="bookmark_remove">删除书签</string>
<string name="bookmarks">书签</string>
<string name="bookmark_removed">删除书签</string>
<string name="bookmark_added">加入书签</string>
<string name="undo">撤销</string>
<string name="removed_from_history">从历史中删除</string>
<string name="dns_over_https">DNS over HTTPS</string>
<string name="default_mode">默认模式</string>
<string name="detect_reader_mode">自动检测阅读器模式</string>
<string name="detect_reader_mode_summary">自动检测漫画是否为网络漫画</string>
<string name="disable_battery_optimization">禁用电池优化</string>
<string name="disable_battery_optimization_summary">帮助进行背景更新检查</string>
<string name="crash_text">出了点问题. 请向开发人员提交一份错误报告以帮助我们修复它.</string>
<string name="send">发送</string>
</resources> </resources>

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<bool name="is_tablet">false</bool> <bool name="is_tablet">false</bool>
<bool name="light_status_bar">true</bool> <bool name="light_status_bar">true</bool>
<bool name="light_navigation_bar">false</bool> <bool name="light_navigation_bar">false</bool>
<bool name="elevation_overlay_enabled">false</bool>
</resources> </resources>

@ -15,7 +15,7 @@
<dimen name="manga_list_details_item_height">120dp</dimen> <dimen name="manga_list_details_item_height">120dp</dimen>
<dimen name="bookmark_item_height">120dp</dimen> <dimen name="bookmark_item_height">120dp</dimen>
<dimen name="bookmark_list_spacing">4dp</dimen> <dimen name="bookmark_list_spacing">4dp</dimen>
<dimen name="chapter_list_item_height">62dp</dimen> <dimen name="chapter_list_item_height">48dp</dimen>
<dimen name="preferred_grid_width">120dp</dimen> <dimen name="preferred_grid_width">120dp</dimen>
<dimen name="header_height">48dp</dimen> <dimen name="header_height">48dp</dimen>
<dimen name="list_footer_height_inner">36dp</dimen> <dimen name="list_footer_height_inner">36dp</dimen>
@ -23,10 +23,15 @@
<dimen name="screen_padding">16dp</dimen> <dimen name="screen_padding">16dp</dimen>
<dimen name="selection_stroke_width">2dp</dimen> <dimen name="selection_stroke_width">2dp</dimen>
<dimen name="list_selector_corner">12dp</dimen> <dimen name="list_selector_corner">12dp</dimen>
<dimen name="toolbar_button_margin">10dp</dimen>
<dimen name="widget_cover_height">116dp</dimen>
<dimen name="widget_cover_width">84dp</dimen>
<dimen name="search_suggestions_manga_height">124dp</dimen> <dimen name="search_suggestions_manga_height">124dp</dimen>
<dimen name="search_suggestions_manga_spacing">4dp</dimen> <dimen name="search_suggestions_manga_spacing">4dp</dimen>
<dimen name="bottom_sheet_width">0dp</dimen> <dimen name="bottom_sheet_width">0dp</dimen>
<dimen name="dialog_radius">8dp</dimen> <dimen name="dialog_radius">8dp</dimen>
<dimen name="appwidget_corner_radius_inner">8dp</dimen>
</resources> </resources>

@ -300,4 +300,8 @@
<string name="disable_battery_optimization_summary">Helps with background updates checks</string> <string name="disable_battery_optimization_summary">Helps with background updates checks</string>
<string name="crash_text">Something went wrong. Please submit a bug report to the developers to help us fix it.</string> <string name="crash_text">Something went wrong. Please submit a bug report to the developers to help us fix it.</string>
<string name="send">Send</string> <string name="send">Send</string>
<string name="disable_all">Disable all</string>
<string name="use_fingerprint">Use fingerprint if available</string>
<string name="appwidget_shelf_description">Manga from your favourites</string>
<string name="appwidget_recent_description">Your recently read manga</string>
</resources> </resources>

@ -86,14 +86,6 @@
<item name="iconPadding">16dp</item> <item name="iconPadding">16dp</item>
</style> </style>
<style name="Widget.Kotatsu.Switch" parent="Widget.Material3.CompoundButton.Switch">
<item name="android:thumb">@drawable/switch_thumb</item>
<item name="thumbTint">@color/selector_switch_thumb</item>
<item name="track">@drawable/switch_track</item>
<item name="trackTint">@color/selector_switch_track</item>
<item name="materialThemeOverlay">@style/ThemeOverlay.Kotatsu.Switch</item>
</style>
<style name="Widget.Kotatsu.RecyclerView" parent=""> <style name="Widget.Kotatsu.RecyclerView" parent="">
<item name="fastScrollHorizontalThumbDrawable">@drawable/list_thumb</item> <item name="fastScrollHorizontalThumbDrawable">@drawable/list_thumb</item>
<item name="fastScrollVerticalThumbDrawable">@drawable/list_thumb</item> <item name="fastScrollVerticalThumbDrawable">@drawable/list_thumb</item>
@ -114,10 +106,6 @@
<item name="android:insetBottom">2dp</item> <item name="android:insetBottom">2dp</item>
</style> </style>
<style name="ThemeOverlay.Kotatsu.Switch" parent="">
<item name="elevationOverlayEnabled">@bool/elevation_overlay_enabled</item>
</style>
<style name="ThemeOverlay.Kotatsu.MainToolbar" parent=""> <style name="ThemeOverlay.Kotatsu.MainToolbar" parent="">
<item name="colorControlHighlight">@color/selector_overlay</item> <item name="colorControlHighlight">@color/selector_overlay</item>
</style> </style>
@ -133,7 +121,7 @@
<item name="android:textColor">?attr/colorOnBackground</item> <item name="android:textColor">?attr/colorOnBackground</item>
</style> </style>
<style name="TextAppearance.Kotatsu.Button" parent="TextAppearance.Material3.TitleSmall"/> <style name="TextAppearance.Kotatsu.Button" parent="TextAppearance.Material3.TitleSmall" />
<style name="TextAppearance.Kotatsu.Preference.Secondary" parent="TextAppearance.Material3.BodySmall" /> <style name="TextAppearance.Kotatsu.Preference.Secondary" parent="TextAppearance.Material3.BodySmall" />
@ -181,4 +169,8 @@
<item name="android:dialogLayout">@layout/preference_dialog_autocompletetextview</item> <item name="android:dialogLayout">@layout/preference_dialog_autocompletetextview</item>
</style> </style>
<style name="Preference.SwitchPreferenceCompat.M3" parent="Preference.SwitchPreferenceCompat.Material">
<item name="android:widgetLayout">@layout/preference_widget_material_switch</item>
</style>
</resources> </resources>

@ -68,10 +68,11 @@
<item name="toolbarStyle">@style/Widget.Material3.Toolbar</item> <item name="toolbarStyle">@style/Widget.Material3.Toolbar</item>
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item> <item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
<item name="tabStyle">@style/Widget.Kotatsu.Tabs</item> <item name="tabStyle">@style/Widget.Kotatsu.Tabs</item>
<item name="switchStyle">@style/Widget.Kotatsu.Switch</item>
<item name="materialCardViewStyle">@style/Widget.Material3.CardView.Filled</item> <item name="materialCardViewStyle">@style/Widget.Material3.CardView.Filled</item>
<item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item> <item name="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</item>
<item name="listItemTextViewStyle">@style/Widget.Kotatsu.ListItemTextView</item> <item name="listItemTextViewStyle">@style/Widget.Kotatsu.ListItemTextView</item>
<item name="materialSwitchStyle">@style/Widget.Material3.CompoundButton.MaterialSwitch</item>
<item name="switchPreferenceCompatStyle">@style/Preference.SwitchPreferenceCompat.M3</item>
<!-- Text appearance --> <!-- Text appearance -->
<item name="actionMenuTextAppearance">@style/TextAppearance.Kotatsu.Menu</item> <item name="actionMenuTextAppearance">@style/TextAppearance.Kotatsu.Menu</item>
@ -102,4 +103,8 @@
<item name="toolbarNavigationButtonStyle">@style/Theme.Kotatsu.ActionMode.CloseButton</item> <item name="toolbarNavigationButtonStyle">@style/Theme.Kotatsu.ActionMode.CloseButton</item>
</style> </style>
<style name="Theme.Kotatsu.AppWidgetContainer" parent="@style/Theme.MaterialComponents.Light">
<item name="android:colorBackground">@android:color/transparent</item>
<item name="android:panelColorBackground">@color/kotatsu_primaryContainer</item>
</style>
</resources> </resources>

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config xmlns:tools="http://schemas.android.com/tools"> <network-security-config
xmlns:tools="http://schemas.android.com/tools">
<!-- Need to allow cleartext traffic for some sources --> <!-- Need to allow cleartext traffic for some sources -->
<base-config <base-config
cleartextTrafficPermitted="true" cleartextTrafficPermitted="true"
@ -8,7 +9,7 @@
<!-- Trust preinstalled CAs --> <!-- Trust preinstalled CAs -->
<certificates src="system" /> <certificates src="system" />
<!-- Additionally trust user added CAs --> <!-- Additionally, trust user added CAs -->
<certificates <certificates
src="user" src="user"
tools:ignore="AcceptsUserCertificates" /> tools:ignore="AcceptsUserCertificates" />

@ -1,12 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider <appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:description="@string/appwidget_recent_description"
android:initialLayout="@layout/widget_recent" android:initialLayout="@layout/widget_recent"
android:minWidth="110dp" android:minWidth="120dp"
android:minHeight="110dp" android:minHeight="40dp"
android:minResizeWidth="40dp" android:minResizeWidth="120dp"
android:minResizeHeight="40dp" android:minResizeHeight="40dp"
android:previewImage="@drawable/ic_appwidget_recent" android:previewImage="@drawable/ic_appwidget_recent"
android:resizeMode="horizontal|vertical" android:resizeMode="horizontal|vertical"
android:targetCellWidth="2"
android:targetCellHeight="2"
android:updatePeriodMillis="0" android:updatePeriodMillis="0"
android:widgetCategory="home_screen" /> android:widgetCategory="home_screen"
tools:ignore="UnusedAttribute" />

@ -1,13 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<appwidget-provider <appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:configure="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity" android:configure="org.koitharu.kotatsu.widget.shelf.ShelfConfigActivity"
android:description="@string/appwidget_shelf_description"
android:initialLayout="@layout/widget_shelf" android:initialLayout="@layout/widget_shelf"
android:minWidth="110dp" android:minWidth="160dp"
android:minHeight="110dp" android:minHeight="120dp"
android:minResizeWidth="40dp" android:minResizeWidth="120dp"
android:minResizeHeight="40dp" android:minResizeHeight="40dp"
android:previewImage="@drawable/ic_appwidget_shelf" android:previewImage="@drawable/ic_appwidget_shelf"
android:resizeMode="horizontal|vertical" android:resizeMode="horizontal|vertical"
android:targetCellWidth="3"
android:targetCellHeight="2"
android:updatePeriodMillis="0" android:updatePeriodMillis="0"
android:widgetCategory="home_screen" /> android:widgetCategory="home_screen"
android:widgetFeatures="reconfigurable"
tools:ignore="UnusedAttribute" />

@ -5,7 +5,7 @@ Kotatsu - приложения для чтения манги с открыты
- Поиск манги по имени и жанрам - Поиск манги по имени и жанрам
- История чтения - История чтения
- Избранное с пользовательскими категориями - Избранное с пользовательскими категориями
- Возможность сохранять мангу и читать её оффлайн. Поддержка сторонних комиксов в формате CBZ - Возможность сохранять мангу и читать её офлайн. Поддержка сторонних комиксов в формате CBZ
- Интерфейс также оптимизирован для планшетов - Интерфейс также оптимизирован для планшетов
- Поддержка манхвы (Webtoon) - Поддержка манхвы (Webtoon)
- Уведомления о новых главах и лента обновлений - Уведомления о новых главах и лента обновлений

Loading…
Cancel
Save