Merge branch 'devel' into feature/shikimori

pull/125/head
Koitharu 4 years ago
commit e6e37aec47
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -0,0 +1,29 @@
**PLEASE READ THIS**
I acknowledge that:
- I have updated to the latest version of the app (https://github.com/KotatsuApp/Kotatsu/releases/latest)
- If this is an issue with a parser, that I should be opening an issue in https://github.com/KotatsuApp/kotatsu-parsers
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
- I will fill out the title and the information in this template
Note that the issue will be automatically closed if you do not fill out the title or requested information.
**DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT**
---
## Device information
* Kotatsu version: ?
* Android version: ?
* Device: ?
## Steps to reproduce
1. First step
2. Second step
## Issue/Request
?
## Other details
Additional details and attachments.

@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: ⚠️ Source issue
url: https://github.com/nv95/kotatsu-parsers/issues/new
url: https://github.com/KotatsuApp/kotatsu-parsers/issues/new
about: Issues and requests for sources should be opened in the kotatsu-parsers repository instead

@ -44,7 +44,7 @@ body:
label: Kotatsu version
description: You can find your Kotatsu version in **Settings → About**.
placeholder: |
Example: "3.3.1"
Example: "3.3"
validations:
required: true
@ -87,7 +87,5 @@ body:
required: true
- 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
- label: I have updated the app to version **[3.3.1](https://github.com/KotatsuApp/Kotatsu/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true

@ -21,6 +21,16 @@ body:
placeholder: |
Additional details and attachments.
- type: input
id: kotatsu-version
attributes:
label: Kotatsu version
description: You can find your Kotatsu version in **Settings → About**.
placeholder: |
Example: "3.3"
validations:
required: true
- type: checkboxes
id: acknowledgements
attributes:
@ -33,7 +43,5 @@ body:
required: true
- 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
- label: I have updated the app to version **[3.3.1](https://github.com/KotatsuApp/Kotatsu/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true

@ -0,0 +1,29 @@
name: Issue moderator
on:
issues:
types: [opened, edited, reopened]
issue_comment:
types: [created]
jobs:
moderate:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
auto-close-rules: |
[
{
"type": "body",
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
"message": "The acknowledgment section was not removed."
},
{
"type": "body",
"regex": ".*\\* (Kotatsu version|Android version|Device): \\?.*",
"message": "Requested information in the template was not filled out."
}
]

1
.gitignore vendored

@ -13,6 +13,7 @@
/.idea/kotlinScripting.xml
/.idea/deploymentTargetDropDown.xml
/.idea/androidTestResultsUserPreferences.xml
/.idea/render.experimental.xml
.DS_Store
/build
/captures

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RenderSettings">
<option name="quality" value="0.25" />
</component>
</project>

@ -10,7 +10,7 @@ Kotatsu is a free and open source manga reader for Android.
alt="Get it on F-Droid"
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)

@ -14,8 +14,8 @@ android {
applicationId 'org.koitharu.kotatsu'
minSdkVersion 21
targetSdkVersion 32
versionCode 410
versionName '3.3.1'
versionCode 411
versionName '3.3.2'
generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

@ -94,6 +94,7 @@ class KotatsuApp : Application() {
ReportField.PHONE_MODEL,
ReportField.CRASH_CONFIGURATION,
ReportField.STACK_TRACE,
ReportField.CUSTOM_DATA,
ReportField.SHARED_PREFERENCES,
)
dialog {

@ -6,14 +6,12 @@ import androidx.annotation.CallSuper
import androidx.annotation.StringRes
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import org.koin.android.ext.android.inject
import org.koitharu.kotatsu.base.ui.util.RecyclerViewOwner
import org.koitharu.kotatsu.base.ui.util.WindowInsetsDelegate
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.SettingsHeadersFragment
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())
}
}

@ -17,19 +17,28 @@
package org.koitharu.kotatsu.base.ui.widgets
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.Button
import android.widget.FrameLayout
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.StringRes
import androidx.core.graphics.drawable.DrawableCompat
import androidx.core.view.postDelayed
import org.koitharu.kotatsu.R
import com.google.android.material.color.MaterialColors
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.databinding.FadingSnackbarLayoutBinding
import org.koitharu.kotatsu.utils.ext.getThemeColorStateList
import com.google.android.material.R as materialR
private const val ENTER_DURATION = 300L
private const val EXIT_DURATION = 200L
private const val SHORT_DURATION = 1_500L
private const val LONG_DURATION = 2_750L
private const val SHORT_DURATION_MS = 1_500L
private const val LONG_DURATION_MS = 2_750L
/**
* A custom snackbar implementation allowing more control over placement and entry/exit animations.
*
@ -40,16 +49,13 @@ private const val LONG_DURATION = 2_750L
class FadingSnackbar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) {
private val message: TextView
private val action: Button
private val binding = FadingSnackbarLayoutBinding.inflate(LayoutInflater.from(context), this)
init {
val view = LayoutInflater.from(context).inflate(R.layout.fading_snackbar_layout, this, true)
message = view.findViewById(R.id.snackbar_text)
action = view.findViewById(R.id.snackbar_action)
binding.snackbarLayout.background = createThemedBackground()
}
fun dismiss() {
@ -62,33 +68,66 @@ class FadingSnackbar @JvmOverloads constructor(
}
fun show(
messageText: CharSequence? = null,
@StringRes actionId: Int? = null,
longDuration: Boolean = true,
actionClick: () -> Unit = { dismiss() },
dismissListener: () -> Unit = { }
messageText: CharSequence?,
@StringRes actionId: Int = 0,
duration: Int = Snackbar.LENGTH_SHORT,
onActionClick: (FadingSnackbar.() -> Unit)? = null,
onDismiss: (() -> Unit)? = null,
) {
message.text = messageText
if (actionId != null) {
action.run {
binding.snackbarText.text = messageText
if (actionId != 0) {
with(binding.snackbarAction) {
visibility = VISIBLE
text = context.getString(actionId)
setOnClickListener {
actionClick()
onActionClick?.invoke(this@FadingSnackbar) ?: dismiss()
}
}
} else {
action.visibility = GONE
binding.snackbarAction.visibility = GONE
}
alpha = 0f
visibility = VISIBLE
animate()
.alpha(1f)
.duration = ENTER_DURATION
val showDuration = ENTER_DURATION + if (longDuration) LONG_DURATION else SHORT_DURATION
postDelayed(showDuration) {
if (duration == Snackbar.LENGTH_INDEFINITE) {
return
}
val durationMs = ENTER_DURATION + if (duration == Snackbar.LENGTH_LONG) LONG_DURATION_MS else SHORT_DURATION_MS
postDelayed(durationMs) {
dismiss()
dismissListener()
onDismiss?.invoke()
}
}
private fun createThemedBackground(): Drawable {
val backgroundColor = MaterialColors.layer(this, materialR.attr.colorSurface, materialR.attr.colorOnSurface, 1f)
val shapeAppearanceModel = ShapeAppearanceModel.builder(
context,
materialR.style.ShapeAppearance_Material3_Corner_ExtraSmall,
0
).build()
val background = createMaterialShapeDrawableBackground(
backgroundColor,
shapeAppearanceModel,
)
val backgroundTint = context.getThemeColorStateList(materialR.attr.colorSurfaceInverse)
return if (backgroundTint != null) {
val wrappedDrawable = DrawableCompat.wrap(background)
DrawableCompat.setTintList(wrappedDrawable, backgroundTint)
wrappedDrawable
} else {
DrawableCompat.wrap(background)
}
}
private fun createMaterialShapeDrawableBackground(
@ColorInt backgroundColor: Int,
shapeAppearanceModel: ShapeAppearanceModel,
): MaterialShapeDrawable {
val background = MaterialShapeDrawable(shapeAppearanceModel)
background.fillColor = ColorStateList.valueOf(backgroundColor)
return background
}
}

@ -15,11 +15,11 @@ class BackupZipOutput(val file: File) : Closeable {
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))
}
suspend fun finish() {
suspend fun finish() = runInterruptible(Dispatchers.IO) {
output.finish()
}

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

@ -126,6 +126,10 @@ class AppSettings(context: Context) {
get() = prefs.getString(KEY_APP_PASSWORD, null)
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>
get() = prefs.getString(KEY_SOURCES_ORDER, null)
?.split('|')
@ -286,6 +290,7 @@ class AppSettings(context: Context) {
const val KEY_READER_MODE_DETECT = "reader_mode_detect"
const val KEY_APP_PASSWORD = "app_password"
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_ZOOM_MODE = "zoom_mode"
const val KEY_BACKUP = "backup"
@ -310,9 +315,6 @@ class AppSettings(context: Context) {
const val KEY_APP_UPDATE = "app_update"
const val KEY_APP_UPDATE_AUTO = "app_update_auto"
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_ALWAYS = 1

@ -21,9 +21,11 @@ import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.coroutines.launch
import org.acra.ktx.sendWithAcra
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
@ -37,6 +39,7 @@ import org.koitharu.kotatsu.core.os.ShortcutsRepository
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.details.ui.adapter.BranchesAdapter
import org.koitharu.kotatsu.download.ui.service.DownloadService
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
@ -82,7 +85,7 @@ class DetailsActivity :
viewModel.onMangaRemoved.observe(this, ::onMangaRemoved)
viewModel.onError.observe(this, ::onError)
viewModel.onShowToast.observe(this) {
binding.snackbar.show(messageText = getString(it), longDuration = false)
binding.snackbar.show(messageText = getString(it))
}
registerReceiver(downloadReceiver, IntentFilter(DownloadService.ACTION_DOWNLOAD_COMPLETE))
@ -115,6 +118,21 @@ class DetailsActivity :
Toast.makeText(this, e.getDisplayMessage(resources), Toast.LENGTH_LONG).show()
finishAfterTransition()
}
e is ParseException || e is IllegalArgumentException || e is IllegalStateException -> {
binding.snackbar.show(
messageText = e.getDisplayMessage(resources),
actionId = R.string.report,
duration = if (viewModel.manga.value?.chapters == null) {
Snackbar.LENGTH_INDEFINITE
} else {
Snackbar.LENGTH_LONG
},
onActionClick = {
e.sendWithAcra()
dismiss()
}
)
}
else -> {
binding.snackbar.show(e.getDisplayMessage(resources))
}

@ -4,7 +4,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.asFlow
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
@ -32,6 +31,7 @@ import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import java.io.IOException
class DetailsViewModel(
intent: MangaIntent,

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.details.ui
import androidx.core.os.LocaleListCompat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import org.acra.ACRA
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.core.exceptions.MangaNotFoundException
@ -13,6 +14,7 @@ import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.details.ui.model.toListItem
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
@ -20,6 +22,7 @@ import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.utils.ext.iterator
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.setCurrentManga
class MangaDetailsDelegate(
private val intent: MangaIntent,
@ -32,6 +35,7 @@ class MangaDetailsDelegate(
private val mangaData = MutableStateFlow(intent.manga)
val selectedBranch = MutableStateFlow<String?>(null)
// Remote manga for saved and saved for remote
val relatedManga = MutableStateFlow<Manga?>(null)
val manga: StateFlow<Manga?>
@ -41,6 +45,7 @@ class MangaDetailsDelegate(
suspend fun doLoad() {
var manga = mangaDataRepository.resolveIntent(intent)
?: throw MangaNotFoundException("Cannot find manga")
ACRA.setCurrentManga(manga)
mangaData.value = manga
manga = MangaRepository(manga.source).getDetails(manga)
// find default branch

@ -29,7 +29,6 @@ import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.scrobbling.ui.selector.ScrobblingSelectorBottomSheet
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.requireValue
class ScrobblingInfoBottomSheet :
BaseBottomSheet<SheetScrobblingBinding>(),

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

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

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

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

@ -96,6 +96,9 @@ class ProtectActivity :
}
private fun useFingerprint(): Boolean {
if (!viewModel.isBiometricEnabled) {
return false
}
if (BiometricManager.from(this).canAuthenticate(BIOMETRIC_WEAK) != BIOMETRIC_SUCCESS) {
return false
}

@ -19,6 +19,9 @@ class ProtectViewModel(
val onUnlockSuccess = SingleLiveEvent<Unit>()
val isBiometricEnabled
get() = settings.isBiometricProtectionEnabled
fun tryUnlock(password: String) {
if (job?.isActive == true) {
return

@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.acra.ktx.sendWithAcra
import org.koin.android.ext.android.get
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
@ -214,6 +215,8 @@ class ReaderActivity :
val resolveTextId = ExceptionResolver.getResolveStringId(e)
if (resolveTextId != 0) {
dialog.setPositiveButton(resolveTextId, listener)
} else {
dialog.setPositiveButton(R.string.report, listener)
}
dialog.show()
}
@ -368,7 +371,11 @@ class ReaderActivity :
override fun onClick(dialog: DialogInterface?, which: Int) {
if (which == DialogInterface.BUTTON_POSITIVE) {
dialog?.dismiss()
if (ExceptionResolver.canResolve(exception)) {
tryResolve(exception)
} else {
exception.sendWithAcra()
}
} else {
onCancel(dialog)
}

@ -6,9 +6,9 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import java.util.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.acra.ACRA
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaDataRepository
import org.koitharu.kotatsu.base.domain.MangaIntent
@ -32,6 +32,8 @@ import org.koitharu.kotatsu.utils.SingleLiveEvent
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
import org.koitharu.kotatsu.utils.ext.setCurrentManga
import java.util.*
private const val BOUNDS_PAGE_OFFSET = 2
private const val PAGES_TRIM_THRESHOLD = 120
@ -257,6 +259,7 @@ class ReaderViewModel(
private fun loadImpl() {
loadingJob = launchLoadingJob(Dispatchers.Default) {
var manga = dataRepository.resolveIntent(intent) ?: throw MangaNotFoundException("Cannot find manga")
ACRA.setCurrentManga(manga)
mangaData.value = manga
val repo = MangaRepository(manga.source)
manga = repo.getDetails(manga)

@ -16,6 +16,7 @@ abstract class BasePageHolder<B : ViewBinding>(
exceptionResolver: ExceptionResolver
) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback {
@Suppress("LeakingThis")
protected val delegate = PageHolderDelegate(loader, settings, this, exceptionResolver)
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.suspendCoroutine
@Suppress("LeakingThis")
abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
private val loader: PageLoader,
private val settings: AppSettings,
private val exceptionResolver: ExceptionResolver
private val exceptionResolver: ExceptionResolver,
) : RecyclerView.Adapter<H>() {
private val differ = AsyncListDiffer(this, DiffCallback())

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

@ -91,7 +91,7 @@ class SettingsActivity :
val fm = supportFragmentManager
val fragment = fm.fragmentFactory.instantiate(classLoader, pref.fragment ?: return false)
fragment.arguments = pref.extras
// fragment.setTargetFragment(caller, 0)
fragment.setTargetFragment(caller, 0)
openFragment(fragment)
return true
}
@ -122,6 +122,7 @@ class SettingsActivity :
ACTION_READER -> ReaderSettingsFragment()
ACTION_SUGGESTIONS -> SuggestionsSettingsFragment()
ACTION_SHIKIMORI -> ShikimoriSettingsFragment()
ACTION_TRACKER -> TrackerSettingsFragment()
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL
)
@ -146,6 +147,7 @@ class SettingsActivity :
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_TRACKER = "${BuildConfig.APPLICATION_ID}.action.MANAGE_TRACKER"
private const val ACTION_SOURCE = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
private const val ACTION_SHIKIMORI = "${BuildConfig.APPLICATION_ID}.action.MANAGE_SHIKIMORI_SETTINGS"
private const val EXTRA_SOURCE = "source"
@ -166,6 +168,10 @@ class SettingsActivity :
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SUGGESTIONS)
fun newTrackerSettingsIntent(context: Context) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_TRACKER)
fun newSourceSettingsIntent(context: Context, source: MangaSource) =
Intent(context, SettingsActivity::class.java)
.setAction(ACTION_SOURCE)

@ -1,5 +1,7 @@
package org.koitharu.kotatsu.settings.protect
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@ -7,9 +9,11 @@ import android.view.KeyEvent
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.EditorInfo
import android.widget.CompoundButton
import android.widget.TextView
import androidx.core.graphics.Insets
import androidx.core.view.isGone
import androidx.core.view.isVisible
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
@ -18,7 +22,7 @@ import org.koitharu.kotatsu.databinding.ActivitySetupProtectBinding
private const val MIN_PASSWORD_LENGTH = 4
class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWatcher,
View.OnClickListener, TextView.OnEditorActionListener {
View.OnClickListener, TextView.OnEditorActionListener, CompoundButton.OnCheckedChangeListener {
private val viewModel by viewModel<ProtectSetupViewModel>()
@ -31,6 +35,9 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
binding.buttonNext.setOnClickListener(this)
binding.buttonCancel.setOnClickListener(this)
binding.switchBiometric.isChecked = viewModel.isBiometricEnabled
binding.switchBiometric.setOnCheckedChangeListener(this)
viewModel.isSecondStep.observe(this, this::onStepChanged)
viewModel.onPasswordSet.observe(this) {
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 {
return if (actionId == EditorInfo.IME_ACTION_DONE && binding.buttonNext.isEnabled) {
binding.buttonNext.performClick()
@ -85,6 +96,7 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
private fun onStepChanged(isSecondStep: Boolean) {
binding.buttonCancel.isGone = isSecondStep
binding.switchBiometric.isVisible = isSecondStep && isBiometricAvailable()
if (isSecondStep) {
binding.layoutPassword.helperText = getString(R.string.repeat_password)
binding.buttonNext.setText(R.string.confirm)
@ -93,4 +105,9 @@ class ProtectSetupActivity : BaseActivity<ActivitySetupProtectBinding>(), TextWa
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 onClearText = SingleLiveEvent<Unit>()
val isBiometricEnabled
get() = settings.isBiometricProtectionEnabled
fun onNextClick(password: String) {
if (firstPassword.value == null) {
firstPassword.value = password
@ -35,4 +38,8 @@ class ProtectSetupViewModel(
}
}
}
fun setBiometricEnabled(isEnabled: Boolean) {
settings.isBiometricProtectionEnabled = isEnabled
}
}

@ -29,6 +29,6 @@ class RingtonePickContract(private val title: String?) : ActivityResultContract<
}
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>) {
db.withTransaction {
db.suggestionDao.deleteAll()
suggestions.forEach { x ->
val tags = x.manga.tags.toEntities()
suggestions.forEach { (manga, relevance) ->
val tags = manga.tags.toEntities()
db.tagsDao.upsert(tags)
db.mangaDao.upsert(x.manga.toEntity(), tags)
db.mangaDao.upsert(manga.toEntity(), tags)
db.suggestionDao.upsert(
SuggestionEntity(
mangaId = x.manga.id,
relevance = x.relevance,
mangaId = manga.id,
relevance = relevance,
createdAt = System.currentTimeMillis(),
)
)

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

@ -35,7 +35,7 @@ class ScreenOrientationHelper(private val activity: Activity) {
isLandscape = !isLandscape
}
fun observeAutoOrientation() = callbackFlow<Boolean> {
fun observeAutoOrientation() = callbackFlow {
val observer = object : ContentObserver(Handler(activity.mainLooper)) {
override fun onChange(selfChange: Boolean) {
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,11 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.liveData
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import org.koitharu.kotatsu.utils.BufferedObserver
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
fun <T> LiveData<T?>.observeNotNull(owner: LifecycleOwner, observer: Observer<T>) {
this.observe(owner) {

@ -2,14 +2,16 @@ package org.koitharu.kotatsu.utils.ext
import android.content.ActivityNotFoundException
import android.content.res.Resources
import java.io.FileNotFoundException
import java.net.SocketTimeoutException
import okio.FileNotFoundException
import org.acra.ACRA
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
import org.koitharu.kotatsu.parsers.model.Manga
import java.net.SocketTimeoutException
fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
is AuthRequiredException -> resources.getString(R.string.auth_required)
@ -23,3 +25,5 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
is WrongPasswordException -> resources.getString(R.string.wrong_password)
else -> localizedMessage ?: resources.getString(R.string.error_occurred)
}
fun ACRA.setCurrentManga(manga: Manga?) = errorReporter.putCustomData("manga", manga?.publicUrl.toString())

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

@ -7,13 +7,15 @@ import android.widget.RemoteViewsService
import coil.ImageLoader
import coil.executeBlocking
import coil.request.ImageRequest
import coil.size.Scale
import coil.size.Size
import coil.transform.RoundedCornersTransformation
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.history.domain.HistoryRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.requireBitmap
import java.io.IOException
class RecentListFactory(
private val context: Context,
@ -22,9 +24,15 @@ class RecentListFactory(
) : RemoteViewsService.RemoteViewsFactory {
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
@ -41,14 +49,18 @@ class RecentListFactory(
override fun getViewAt(position: Int): RemoteViews {
val views = RemoteViews(context.packageName, R.layout.item_recent)
val item = dataSet[position]
try {
val cover = coil.executeBlocking(
runCatching {
coil.executeBlocking(
ImageRequest.Builder(context)
.data(item.coverUrl)
.size(coverSize)
.scale(Scale.FILL)
.transformations(transformation)
.build()
).requireBitmap()
}.onSuccess { cover ->
views.setImageViewBitmap(R.id.imageView_cover, cover)
} catch (e: IOException) {
}.onFailure {
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
}
val intent = Intent()
@ -61,6 +73,5 @@ class RecentListFactory(
override fun getViewTypeCount() = 1
override fun onDestroy() {
}
override fun onDestroy() = Unit
}

@ -10,8 +10,6 @@ import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
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 org.koin.androidx.viewmodel.ext.android.viewModel
import org.koitharu.kotatsu.R
@ -40,9 +38,6 @@ class ShelfConfigActivity : BaseActivity<ActivityCategoriesBinding>(),
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
}
adapter = CategorySelectAdapter(this)
binding.recyclerView.addItemDecoration(
MaterialDividerItemDecoration(this, RecyclerView.VERTICAL)
)
binding.recyclerView.adapter = adapter
binding.buttonDone.isVisible = true
binding.buttonDone.setOnClickListener(this)

@ -7,6 +7,9 @@ import android.widget.RemoteViewsService
import coil.ImageLoader
import coil.executeBlocking
import coil.request.ImageRequest
import coil.size.Scale
import coil.size.Size
import coil.transform.RoundedCornersTransformation
import kotlinx.coroutines.runBlocking
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent
@ -14,20 +17,25 @@ import org.koitharu.kotatsu.core.prefs.AppWidgetConfig
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.requireBitmap
import java.io.IOException
class ShelfListFactory(
private val context: Context,
private val favouritesRepository: FavouritesRepository,
private val coil: ImageLoader,
widgetId: Int
widgetId: Int,
) : RemoteViewsService.RemoteViewsFactory {
private val dataSet = ArrayList<Manga>()
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
@ -52,14 +60,18 @@ class ShelfListFactory(
val views = RemoteViews(context.packageName, R.layout.item_shelf)
val item = dataSet[position]
views.setTextViewText(R.id.textView_title, item.title)
try {
val cover = coil.executeBlocking(
runCatching {
coil.executeBlocking(
ImageRequest.Builder(context)
.data(item.coverUrl)
.size(coverSize)
.scale(Scale.FILL)
.transformations(transformation)
.build()
).requireBitmap()
}.onSuccess { cover ->
views.setImageViewBitmap(R.id.imageView_cover, cover)
} catch (e: IOException) {
}.onFailure {
views.setImageViewResource(R.id.imageView_cover, R.drawable.ic_placeholder)
}
val intent = Intent()

@ -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>

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/kotatsu_primaryContainer" android:state_checked="true" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="@color/kotatsu_primaryContainer" android:state_checked="true" android:state_enabled="false" />
<item android:color="@color/kotatsu_surfaceVariant" android:state_checked="false" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="@color/kotatsu_surfaceVariant" 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="@color/blue_primary" android:state_checked="true" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="@color/blue_primary" android:state_checked="true" android:state_enabled="false" />
<item android:color="@color/kotatsu_onSurfaceVariant" android:state_checked="false" android:state_enabled="true" />
<item android:alpha="@dimen/material_emphasis_disabled" android:color="@color/kotatsu_onSurfaceVariant" android:state_checked="false" android:state_enabled="false" />
</selector>

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

@ -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="@color/selector_switch_thumb" />
<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="@color/selector_switch_track" />
<corners android:radius="56dp" />
<size
android:width="64dp"
android:height="28dp" />
</shape>
</item>
</layer-list>

@ -68,7 +68,7 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.switchmaterial.SwitchMaterial
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/switch_tracker"
android:layout_width="match_parent"
android:layout_height="wrap_content"

@ -62,6 +62,19 @@
</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
android:id="@+id/button_cancel"
style="@style/Widget.Material3.Button.OutlinedButton"

@ -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"?>
<LinearLayout
<ScrollView
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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
@ -68,3 +72,4 @@
tools:visibility="visible" />
</LinearLayout>
</ScrollView>

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2018 Google LLC
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@ -22,15 +21,17 @@
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/snackbar_layout"
style="?attr/snackbarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_small"
android:background="@drawable/fading_snackbar_background"
android:theme="@style/ThemeOverlay.Kotatsu"
android:background="@drawable/design_snackbar_background"
android:elevation="8dp">
<TextView
android:id="@+id/snackbar_text"
style="?attr/snackbarTextViewStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|start"
@ -39,22 +40,22 @@
android:maxLines="4"
android:padding="@dimen/margin_normal"
android:textAlignment="viewStart"
android:textColor="@android:color/white"
android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
android:textColor="@android:color/white"
tools:text="Look at all the wonderful snack bar text..." />
<Button
android:id="@+id/snackbar_action"
style="?borderlessButtonStyle"
style="?attr/snackbarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|end"
android:paddingEnd="@dimen/margin_normal"
android:paddingStart="@dimen/margin_normal"
android:paddingEnd="@dimen/margin_normal"
android:visibility="gone"
tools:targetApi="o"
tools:text="Action"
tools:visibility="visible"
tools:targetApi="o" />
tools:visibility="visible" />
</LinearLayout>

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

@ -18,8 +18,8 @@
android:layout_height="match_parent"
android:orientation="vertical"
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
android:layout_width="match_parent"
@ -53,8 +53,9 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
android:baselineAligned="false"
android:clipChildren="false"
android:orientation="horizontal">
<TextView
android:id="@+id/textView_tags"
@ -72,9 +73,12 @@
android:id="@+id/textView_rating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:drawablePadding="4dp"
android:elegantTextHeight="false"
android:gravity="center_vertical"
android:paddingStart="6dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceBodySmall"
app:drawableEndCompat="@drawable/ic_star"
tools:ignore="RtlSymmetry"
tools:text="9.6" />

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

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

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

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

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

@ -38,7 +38,7 @@
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="?attr/textAppearanceHeadlineSmall"
app:layout_constraintEnd_toStartOf="@id/button_open"
app:layout_constraintEnd_toStartOf="@id/button_menu"
app:layout_constraintStart_toEndOf="@id/imageView_cover"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem[15]" />

@ -2,14 +2,17 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
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
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/stackView"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/item_shelf" />
tools:listitem="@layout/item_recent" />
<TextView
android:id="@+id/textView_holder"

@ -2,7 +2,10 @@
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
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
xmlns:tools="http://schemas.android.com/tools"
@ -21,9 +24,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:shadowColor="@android:color/black"
android:shadowRadius="1"
android:text="@string/you_have_not_favourites_yet"
android:textColor="@android:color/white" />
android:textColor="?android:attr/textColorPrimary" />
</FrameLayout>

@ -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"
app:showAsAction="never" />
<item
android:id="@+id/action_settings"
android:orderInCategory="50"
android:title="@string/settings"
app:showAsAction="never" />
</menu>

@ -299,4 +299,7 @@
<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="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>

@ -298,4 +298,7 @@
<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>

@ -299,4 +299,7 @@
<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>

@ -299,4 +299,7 @@
<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="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>

@ -299,4 +299,7 @@
<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="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>

@ -299,4 +299,7 @@
<string name="crash_text">何か問題が発生しました。開発者にバグレポートを提出し、解決にご協力ください。</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>

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

@ -283,4 +283,23 @@
<string name="edit_category">Изменить категорию</string>
<string name="tracking">Отслеживание</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>

@ -194,7 +194,6 @@
<string name="search_results">Arama sonuçları</string>
<string name="waiting_for_network">Ağ bekleniyor…</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="wrong_password">Yanlış parola</string>
<string name="report_github">GitHub\'da sorun oluştur</string>
@ -300,4 +299,7 @@
<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="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>

@ -288,14 +288,18 @@
<string name="bookmarks">Закладки</string>
<string name="bookmark_removed">Закладка видалена</string>
<string name="bookmark_added">Додано закладку</string>
<string name="undo">Скасувати</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="default_mode">Режим за замовчуванням</string>
<string name="detect_reader_mode_summary">Автоматично визначати, чи є манга вебтуном</string>
<string name="detect_reader_mode">Автовизначення режиму читання</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>

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<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>
</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:textColorAlertDialogListItem">@color/m3_dynamic_default_color_primary_text</item>
</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>

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

@ -15,7 +15,7 @@
<dimen name="manga_list_details_item_height">120dp</dimen>
<dimen name="bookmark_item_height">120dp</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="header_height">48dp</dimen>
<dimen name="list_footer_height_inner">36dp</dimen>
@ -24,10 +24,14 @@
<dimen name="selection_stroke_width">2dp</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_spacing">4dp</dimen>
<dimen name="bottom_sheet_width">0dp</dimen>
<dimen name="dialog_radius">8dp</dimen>
<dimen name="appwidget_corner_radius_inner">8dp</dimen>
</resources>

@ -310,4 +310,8 @@
<string name="status_on_hold">On hold</string>
<string name="status_dropped">Dropped</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>
<string name="report">Report</string>
</resources>

@ -86,13 +86,6 @@
<item name="iconPadding">16dp</item>
</style>
<style name="Widget.Kotatsu.Switch" parent="Widget.Material3.CompoundButton.Switch">
<item name="android:thumb">@drawable/switch_thumb</item>
<item name="track">@drawable/switch_track</item>
<item name="useMaterialThemeColors">false</item>
<item name="materialThemeOverlay">@style/ThemeOverlay.Kotatsu.Switch</item>
</style>
<style name="Widget.Kotatsu.RecyclerView" parent="">
<item name="fastScrollHorizontalThumbDrawable">@drawable/list_thumb</item>
<item name="fastScrollVerticalThumbDrawable">@drawable/list_thumb</item>
@ -113,10 +106,6 @@
<item name="android:insetBottom">2dp</item>
</style>
<style name="ThemeOverlay.Kotatsu.Switch" parent="">
<item name="elevationOverlayEnabled">@bool/elevation_overlay_enabled</item>
</style>
<style name="ThemeOverlay.Kotatsu.MainToolbar" parent="">
<item name="colorControlHighlight">@color/selector_overlay</item>
</style>
@ -180,4 +169,8 @@
<item name="android:dialogLayout">@layout/preference_dialog_autocompletetextview</item>
</style>
<style name="Preference.SwitchPreferenceCompat.M3" parent="Preference.SwitchPreferenceCompat.Material">
<item name="android:widgetLayout">@layout/preference_widget_material_switch</item>
</style>
</resources>

@ -68,10 +68,11 @@
<item name="toolbarStyle">@style/Widget.Material3.Toolbar</item>
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</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="recyclerViewStyle">@style/Widget.Kotatsu.RecyclerView</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 -->
<item name="actionMenuTextAppearance">@style/TextAppearance.Kotatsu.Menu</item>
@ -102,4 +103,8 @@
<item name="toolbarNavigationButtonStyle">@style/Theme.Kotatsu.ActionMode.CloseButton</item>
</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>

@ -1,5 +1,6 @@
<?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 -->
<base-config
cleartextTrafficPermitted="true"
@ -8,7 +9,7 @@
<!-- Trust preinstalled CAs -->
<certificates src="system" />
<!-- Additionally trust user added CAs -->
<!-- Additionally, trust user added CAs -->
<certificates
src="user"
tools:ignore="AcceptsUserCertificates" />

@ -1,12 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
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:minWidth="110dp"
android:minHeight="110dp"
android:minResizeWidth="40dp"
android:minWidth="120dp"
android:minHeight="40dp"
android:minResizeWidth="120dp"
android:minResizeHeight="40dp"
android:previewImage="@drawable/ic_appwidget_recent"
android:resizeMode="horizontal|vertical"
android:targetCellWidth="2"
android:targetCellHeight="2"
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"?>
<appwidget-provider
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:description="@string/appwidget_shelf_description"
android:initialLayout="@layout/widget_shelf"
android:minWidth="110dp"
android:minHeight="110dp"
android:minResizeWidth="40dp"
android:minWidth="160dp"
android:minHeight="120dp"
android:minResizeWidth="120dp"
android:minResizeHeight="40dp"
android:previewImage="@drawable/ic_appwidget_shelf"
android:resizeMode="horizontal|vertical"
android:targetCellWidth="3"
android:targetCellHeight="2"
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)
- Уведомления о новых главах и лента обновлений

Loading…
Cancel
Save