diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..e32589680
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE.md
@@ -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.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index b6d4254a1..c7cc0c855 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -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
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/report_issue.yml b/.github/ISSUE_TEMPLATE/report_issue.yml
index d0766690f..4bc2d2de9 100644
--- a/.github/ISSUE_TEMPLATE/report_issue.yml
+++ b/.github/ISSUE_TEMPLATE/report_issue.yml
@@ -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
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/request_feature.yml b/.github/ISSUE_TEMPLATE/request_feature.yml
index 7bd0e002e..b49ba479b 100644
--- a/.github/ISSUE_TEMPLATE/request_feature.yml
+++ b/.github/ISSUE_TEMPLATE/request_feature.yml
@@ -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
\ No newline at end of file
diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml
new file mode 100644
index 000000000..ef256ed06
--- /dev/null
+++ b/.github/workflows/issue_moderator.yml
@@ -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."
+ }
+ ]
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 9b63e14e7..5611db9cb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,7 @@
/.idea/kotlinScripting.xml
/.idea/deploymentTargetDropDown.xml
/.idea/androidTestResultsUserPreferences.xml
+/.idea/render.experimental.xml
.DS_Store
/build
/captures
diff --git a/.idea/render.experimental.xml b/.idea/render.experimental.xml
deleted file mode 100644
index 5cad4e0be..000000000
--- a/.idea/render.experimental.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 6714250e3..1d693109e 100644
--- a/README.md
+++ b/README.md
@@ -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)
diff --git a/app/build.gradle b/app/build.gradle
index 8ab8a1cad..6496c2699 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
diff --git a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt
index 0ab9b2d1e..d7f25396e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/KotatsuApp.kt
@@ -94,6 +94,7 @@ class KotatsuApp : Application() {
ReportField.PHONE_MODEL,
ReportField.CRASH_CONFIGURATION,
ReportField.STACK_TRACE,
+ ReportField.CUSTOM_DATA,
ReportField.SHARED_PREFERENCES,
)
dialog {
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt
index 2125d044c..7db0f6e22 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/BasePreferenceFragment.kt
@@ -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) :
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TextInputDialog.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TextInputDialog.kt
deleted file mode 100644
index 4b5c02ca6..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/dialog/TextInputDialog.kt
+++ /dev/null
@@ -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())
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/FadingSnackbar.kt b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/FadingSnackbar.kt
index 9c85787a7..909252b48 100644
--- a/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/FadingSnackbar.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/base/ui/widgets/FadingSnackbar.kt
@@ -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
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
index f01dc73d9..8a6217d04 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/backup/BackupZipOutput.kt
@@ -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()
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt
index 5a8cd055c..ef20b4fb0 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/exceptions/CloudFlareProtectedException.kt
@@ -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
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
index e89a782df..166d9a330 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettings.kt
@@ -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
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
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
index 44ee1e7ff..84155b516 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsActivity.kt
@@ -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))
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
index 1ef416990..71a6c49fb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
@@ -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,
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
index 07f03dbda..c6c45ecc1 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/MangaDetailsDelegate.kt
@@ -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(null)
+
// Remote manga for saved and saved for remote
val relatedManga = MutableStateFlow(null)
val manga: StateFlow
@@ -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
diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoBottomSheet.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoBottomSheet.kt
index 37fa78f5a..8347b0a7e 100644
--- a/app/src/main/java/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoBottomSheet.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoBottomSheet.kt
@@ -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(),
diff --git a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesEditDelegate.kt b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesEditDelegate.kt
index f7a98c078..cece6607f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesEditDelegate.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/favourites/ui/categories/CategoriesEditDelegate.kt
@@ -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)
diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt
index b27629ce6..fe0daa58c 100644
--- a/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/HistoryListMenuProvider.kt
@@ -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()
diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
index 28556ac48..568bb6427 100644
--- a/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
@@ -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
diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
index 55cd36559..b327ae030 100644
--- a/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/MainActivity.kt
@@ -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) { _, _ ->
diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt
index 953991baa..0da2ee55f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectActivity.kt
@@ -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
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt
index 69e671c01..85ffe23cb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/main/ui/protect/ProtectViewModel.kt
@@ -19,6 +19,9 @@ class ProtectViewModel(
val onUnlockSuccess = SingleLiveEvent()
+ val isBiometricEnabled
+ get() = settings.isBiometricProtectionEnabled
+
fun tryUnlock(password: String) {
if (job?.isActive == true) {
return
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt
index e4a29b948..dfd3af976 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt
@@ -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()
- tryResolve(exception)
+ if (ExceptionResolver.canResolve(exception)) {
+ tryResolve(exception)
+ } else {
+ exception.sendWithAcra()
+ }
} else {
onCancel(dialog)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
index 931461de0..bb8555941 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
@@ -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)
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt
index 30b696297..d3980c687 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BasePageHolder.kt
@@ -16,6 +16,7 @@ abstract class BasePageHolder(
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)
diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReaderAdapter.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReaderAdapter.kt
index b6adc87b1..d097c1bc2 100644
--- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReaderAdapter.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/BaseReaderAdapter.kt
@@ -11,10 +11,11 @@ import org.koitharu.kotatsu.utils.ext.resetTransformations
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
+@Suppress("LeakingThis")
abstract class BaseReaderAdapter>(
private val loader: PageLoader,
private val settings: AppSettings,
- private val exceptionResolver: ExceptionResolver
+ private val exceptionResolver: ExceptionResolver,
) : RecyclerView.Adapter() {
private val differ = AsyncListDiffer(this, DiffCallback())
diff --git a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchActivity.kt b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchActivity.kt
index 3d075c1b3..ea5551701 100644
--- a/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/search/ui/SearchActivity.kt
@@ -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
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt
index ea566d4bb..91f9c5987 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt
@@ -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)
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt
index a0145362a..f88a8dad9 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupActivity.kt
@@ -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(), TextWatcher,
- View.OnClickListener, TextView.OnEditorActionListener {
+ View.OnClickListener, TextView.OnEditorActionListener, CompoundButton.OnCheckedChangeListener {
private val viewModel by viewModel()
@@ -31,6 +35,9 @@ class ProtectSetupActivity : BaseActivity(), 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(), 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(), 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(), 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)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupViewModel.kt
index 1244c836e..c9013d23d 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupViewModel.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/protect/ProtectSetupViewModel.kt
@@ -22,6 +22,9 @@ class ProtectSetupViewModel(
val onPasswordMismatch = SingleLiveEvent()
val onClearText = SingleLiveEvent()
+ 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
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt b/app/src/main/java/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt
index 1bf8b7856..3920cb32c 100644
--- a/app/src/main/java/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/utils/RingtonePickContract.kt
@@ -29,6 +29,6 @@ class RingtonePickContract(private val title: String?) : ActivityResultContract<
}
override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
- return intent?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
+ return intent?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt
index d334f31ef..398a0a0f0 100644
--- a/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/suggestions/domain/SuggestionRepository.kt
@@ -32,14 +32,14 @@ class SuggestionRepository(
suspend fun replace(suggestions: Iterable) {
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(),
)
)
diff --git a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedMenuProvider.kt b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedMenuProvider.kt
index 6787ff7da..655f02f8b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedMenuProvider.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/tracker/ui/FeedMenuProvider.kt
@@ -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
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ScreenOrientationHelper.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ScreenOrientationHelper.kt
index 6c856f4d4..4cecbd2a8 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ScreenOrientationHelper.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ScreenOrientationHelper.kt
@@ -35,7 +35,7 @@ class ScreenOrientationHelper(private val activity: Activity) {
isLandscape = !isLandscape
}
- fun observeAutoOrientation() = callbackFlow {
+ fun observeAutoOrientation() = callbackFlow {
val observer = object : ContentObserver(Handler(activity.mainLooper)) {
override fun onChange(selfChange: Boolean) {
trySendBlocking(isAutoRotationEnabled)
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt
deleted file mode 100644
index 7276dab57..000000000
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/InsetsExt.kt
+++ /dev/null
@@ -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
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt
index 3ac39ee9c..c4172000f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/LiveDataExt.kt
@@ -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 LiveData.observeNotNull(owner: LifecycleOwner, observer: Observer) {
this.observe(owner) {
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt
similarity index 86%
rename from app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt
rename to app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt
index 22f95e4a4..6dac10c3b 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ThrowableExt.kt
@@ -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)
@@ -22,4 +24,6 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
is SocketTimeoutException -> resources.getString(R.string.network_error)
is WrongPasswordException -> resources.getString(R.string.wrong_password)
else -> localizedMessage ?: resources.getString(R.string.error_occurred)
-}
\ No newline at end of file
+}
+
+fun ACRA.setCurrentManga(manga: Manga?) = errorReporter.putCustomData("manga", manga?.publicUrl.toString())
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt
index 67c30830e..586e40eef 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/ViewExt.kt
@@ -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
diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt
index 3e4125a21..04c25f382 100644
--- a/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/widget/recent/RecentListFactory.kt
@@ -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()
+ 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
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt
index b404b3286..44a2cc632 100644
--- a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfConfigActivity.kt
@@ -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(),
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)
diff --git a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt
index 61fd0d9ab..1676e5a49 100644
--- a/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/widget/shelf/ShelfListFactory.kt
@@ -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()
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()
diff --git a/app/src/main/res/color-v23/selector_switch_thumb.xml b/app/src/main/res/color-v23/selector_switch_thumb.xml
deleted file mode 100644
index ef1a3cc36..000000000
--- a/app/src/main/res/color-v23/selector_switch_thumb.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/color-v23/selector_switch_track.xml b/app/src/main/res/color-v23/selector_switch_track.xml
deleted file mode 100644
index 3779a794a..000000000
--- a/app/src/main/res/color-v23/selector_switch_track.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/color/selector_switch_thumb.xml b/app/src/main/res/color/selector_switch_thumb.xml
deleted file mode 100644
index de8892285..000000000
--- a/app/src/main/res/color/selector_switch_thumb.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/color/selector_switch_track.xml b/app/src/main/res/color/selector_switch_track.xml
deleted file mode 100644
index 7b6c7c468..000000000
--- a/app/src/main/res/color/selector_switch_track.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/badge.xml b/app/src/main/res/drawable/bg_appwidget_card.xml
similarity index 50%
rename from app/src/main/res/drawable/badge.xml
rename to app/src/main/res/drawable/bg_appwidget_card.xml
index a15864699..35a460504 100644
--- a/app/src/main/res/drawable/badge.xml
+++ b/app/src/main/res/drawable/bg_appwidget_card.xml
@@ -2,6 +2,6 @@
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_list_add.xml b/app/src/main/res/drawable/ic_list_add.xml
deleted file mode 100644
index 26ca07f23..000000000
--- a/app/src/main/res/drawable/ic_list_add.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_pause.xml b/app/src/main/res/drawable/ic_pause.xml
deleted file mode 100644
index e63766078..000000000
--- a/app/src/main/res/drawable/ic_pause.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_resume.xml b/app/src/main/res/drawable/ic_resume.xml
deleted file mode 100644
index 448628b18..000000000
--- a/app/src/main/res/drawable/ic_resume.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/switch_thumb.xml b/app/src/main/res/drawable/switch_thumb.xml
deleted file mode 100644
index 12222e7f5..000000000
--- a/app/src/main/res/drawable/switch_thumb.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- -
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/drawable/switch_track.xml b/app/src/main/res/drawable/switch_track.xml
deleted file mode 100644
index 005647294..000000000
--- a/app/src/main/res/drawable/switch_track.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- -
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_category_edit.xml b/app/src/main/res/layout/activity_category_edit.xml
index 4cd3a7afe..93eef7b49 100644
--- a/app/src/main/res/layout/activity_category_edit.xml
+++ b/app/src/main/res/layout/activity_category_edit.xml
@@ -68,7 +68,7 @@
-
+
+
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_list_mode.xml b/app/src/main/res/layout/dialog_list_mode.xml
index cfbd8e018..e8341c954 100644
--- a/app/src/main/res/layout/dialog_list_mode.xml
+++ b/app/src/main/res/layout/dialog_list_mode.xml
@@ -1,70 +1,75 @@
-
+ android:layout_height="wrap_content">
-
-
+ android:layout_margin="16dp"
+ android:orientation="vertical">
-
+
+
+
+
+
+
+
+
+ android:paddingLeft="?attr/dialogPreferredPadding"
+ android:paddingRight="?attr/dialogPreferredPadding"
+ android:singleLine="true"
+ android:text="@string/grid_size"
+ android:visibility="gone"
+ tools:visibility="visible" />
-
+ android:layout_marginHorizontal="16dp"
+ android:stepSize="5"
+ android:valueFrom="50"
+ android:valueTo="150"
+ android:visibility="gone"
+ app:labelBehavior="floating"
+ app:tickVisible="false"
+ tools:value="100"
+ tools:visibility="visible" />
-
-
-
-
-
-
-
\ No newline at end of file
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fading_snackbar_layout.xml b/app/src/main/res/layout/fading_snackbar_layout.xml
index 4c75ce8b9..0ebbb7bdd 100644
--- a/app/src/main/res/layout/fading_snackbar_layout.xml
+++ b/app/src/main/res/layout/fading_snackbar_layout.xml
@@ -1,5 +1,4 @@
-
-
- @style/TextAppearance.Kotatsu.Menu
@@ -102,4 +103,8 @@
- @style/Theme.Kotatsu.ActionMode.CloseButton
+
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
index a1fe42507..d560bd956 100644
--- a/app/src/main/res/xml/network_security_config.xml
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -1,5 +1,6 @@
-
+
-
+
diff --git a/app/src/main/res/xml/widget_recent.xml b/app/src/main/res/xml/widget_recent.xml
index 4f4beea5a..476c83cda 100644
--- a/app/src/main/res/xml/widget_recent.xml
+++ b/app/src/main/res/xml/widget_recent.xml
@@ -1,12 +1,17 @@
+ android:widgetCategory="home_screen"
+ tools:ignore="UnusedAttribute" />
diff --git a/app/src/main/res/xml/widget_shelf.xml b/app/src/main/res/xml/widget_shelf.xml
index 45c37262e..c1e4879de 100644
--- a/app/src/main/res/xml/widget_shelf.xml
+++ b/app/src/main/res/xml/widget_shelf.xml
@@ -1,13 +1,19 @@
+ android:widgetCategory="home_screen"
+ android:widgetFeatures="reconfigurable"
+ tools:ignore="UnusedAttribute" />
diff --git a/metadata/ru/full_description.txt b/metadata/ru/full_description.txt
index 87944e3da..df079ccdd 100644
--- a/metadata/ru/full_description.txt
+++ b/metadata/ru/full_description.txt
@@ -5,7 +5,7 @@ Kotatsu - приложения для чтения манги с открыты
- Поиск манги по имени и жанрам
- История чтения
- Избранное с пользовательскими категориями
-- Возможность сохранять мангу и читать её оффлайн. Поддержка сторонних комиксов в формате CBZ
+- Возможность сохранять мангу и читать её офлайн. Поддержка сторонних комиксов в формате CBZ
- Интерфейс также оптимизирован для планшетов
- Поддержка манхвы (Webtoon)
- Уведомления о новых главах и лента обновлений