diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt index 1a6adcea1..5565f2c58 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -29,6 +29,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration13To14 import org.koitharu.kotatsu.core.db.migrations.Migration14To15 import org.koitharu.kotatsu.core.db.migrations.Migration15To16 import org.koitharu.kotatsu.core.db.migrations.Migration16To17 +import org.koitharu.kotatsu.core.db.migrations.Migration17To18 import org.koitharu.kotatsu.core.db.migrations.Migration1To2 import org.koitharu.kotatsu.core.db.migrations.Migration2To3 import org.koitharu.kotatsu.core.db.migrations.Migration3To4 @@ -53,7 +54,7 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity import org.koitharu.kotatsu.tracker.data.TrackLogEntity import org.koitharu.kotatsu.tracker.data.TracksDao -const val DATABASE_VERSION = 17 +const val DATABASE_VERSION = 18 @Database( entities = [ @@ -108,6 +109,7 @@ fun getDatabaseMigrations(context: Context): Array = arrayOf( Migration14To15(), Migration15To16(), Migration16To17(context), + Migration17To18(), ) fun MangaDatabase(context: Context): MangaDatabase = Room diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt index 98512e8b8..18fe1db04 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt @@ -24,4 +24,5 @@ data class MangaPrefsEntity( @ColumnInfo(name = "cf_brightness") val cfBrightness: Float, @ColumnInfo(name = "cf_contrast") val cfContrast: Float, @ColumnInfo(name = "cf_invert") val cfInvert: Boolean, + @ColumnInfo(name = "cf_grayscale") val cfGrayscale: Boolean, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration17To18.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration17To18.kt new file mode 100644 index 000000000..e80e77b97 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration17To18.kt @@ -0,0 +1,11 @@ +package org.koitharu.kotatsu.core.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration17To18 : Migration(17, 18) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE preferences ADD COLUMN `cf_grayscale` INTEGER NOT NULL DEFAULT 0") + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt index 8a365547c..4e2f9575b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt @@ -98,8 +98,8 @@ class MangaDataRepository @Inject constructor( } private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? { - return if (cfBrightness != 0f || cfContrast != 0f || cfInvert) { - ReaderColorFilter(cfBrightness, cfContrast, cfInvert) + return if (cfBrightness != 0f || cfContrast != 0f || cfInvert || cfGrayscale) { + ReaderColorFilter(cfBrightness, cfContrast, cfInvert, cfGrayscale) } else { null } @@ -111,5 +111,6 @@ class MangaDataRepository @Inject constructor( cfBrightness = 0f, cfContrast = 0f, cfInvert = false, + cfGrayscale = false, ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index 0512630c3..9cb3f2d8e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -301,22 +301,19 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { var readerColorFilter: ReaderColorFilter? get() { - if (!prefs.getBoolean(KEY_CF_ENABLED, false)) { - return null - } - val brightness = prefs.getFloat(KEY_CF_BRIGHTNESS, 0f) - val contrast = prefs.getFloat(KEY_CF_CONTRAST, 0f) - val inverted = prefs.getBoolean(KEY_CF_INVERTED, false) - return ReaderColorFilter(brightness, contrast, inverted) + val brightness = prefs.getFloat(KEY_CF_BRIGHTNESS, ReaderColorFilter.EMPTY.brightness) + val contrast = prefs.getFloat(KEY_CF_CONTRAST, ReaderColorFilter.EMPTY.contrast) + val inverted = prefs.getBoolean(KEY_CF_INVERTED, ReaderColorFilter.EMPTY.isInverted) + val grayscale = prefs.getBoolean(KEY_CF_GRAYSCALE, ReaderColorFilter.EMPTY.isGrayscale) + return ReaderColorFilter(brightness, contrast, inverted, grayscale).takeUnless { it.isEmpty } } set(value) { prefs.edit { - putBoolean(KEY_CF_ENABLED, value != null) - if (value != null) { - putFloat(KEY_CF_BRIGHTNESS, value.brightness) - putFloat(KEY_CF_CONTRAST, value.contrast) - putBoolean(KEY_CF_INVERTED, value.isInverted) - } + val cf = value ?: ReaderColorFilter.EMPTY + putFloat(KEY_CF_BRIGHTNESS, cf.brightness) + putFloat(KEY_CF_CONTRAST, cf.contrast) + putBoolean(KEY_CF_INVERTED, cf.isInverted) + putBoolean(KEY_CF_GRAYSCALE, cf.isGrayscale) } } @@ -573,10 +570,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_32BIT_COLOR = "enhanced_colors" const val KEY_SOURCES_ORDER = "sources_sort_order" const val KEY_SOURCES_CATALOG = "sources_catalog" - const val KEY_CF_ENABLED = "cf_enabled" const val KEY_CF_BRIGHTNESS = "cf_brightness" const val KEY_CF_CONTRAST = "cf_contrast" const val KEY_CF_INVERTED = "cf_inverted" + const val KEY_CF_GRAYSCALE = "cf_grayscale" // About const val KEY_APP_UPDATE = "app_update" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/ReaderColorFilter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/ReaderColorFilter.kt index 9b4f8a828..eb5c73669 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/ReaderColorFilter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/ReaderColorFilter.kt @@ -7,13 +7,17 @@ data class ReaderColorFilter( val brightness: Float, val contrast: Float, val isInverted: Boolean, + val isGrayscale: Boolean, ) { val isEmpty: Boolean - get() = !isInverted && brightness == 0f && contrast == 0f + get() = !isGrayscale && !isInverted && brightness == 0f && contrast == 0f fun toColorFilter(): ColorMatrixColorFilter { val cm = ColorMatrix() + if (isGrayscale) { + cm.grayscale() + } if (isInverted) { cm.inverted() } @@ -49,6 +53,20 @@ data class ReaderColorFilter( 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, ) - set(matrix) + postConcat(ColorMatrix(matrix)) + } + + private fun ColorMatrix.grayscale() { + setSaturation(0f) + } + + companion object { + + val EMPTY = ReaderColorFilter( + brightness = 0.0f, + contrast = 0.0f, + isInverted = false, + isGrayscale = false, + ) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt index 65aaf9c46..650c3b366 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt @@ -16,6 +16,7 @@ import coil.ImageLoader import coil.request.ImageRequest import coil.size.Scale import coil.size.ViewSizeResolver +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider import dagger.hilt.android.AndroidEntryPoint @@ -62,6 +63,7 @@ class ColorFilterConfigActivity : viewBinding.sliderContrast.setLabelFormatter(formatter) viewBinding.sliderBrightness.setLabelFormatter(formatter) viewBinding.switchInvert.setOnCheckedChangeListener(this) + viewBinding.switchGrayscale.setOnCheckedChangeListener(this) viewBinding.buttonDone.setOnClickListener(this) viewBinding.buttonReset.setOnClickListener(this) @@ -84,18 +86,16 @@ class ColorFilterConfigActivity : } } - override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { - viewModel.setInversion(isChecked) + override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { + when (buttonView.id) { + R.id.switch_invert -> viewModel.setInversion(isChecked) + R.id.switch_grayscale -> viewModel.setGrayscale(isChecked) + } } override fun onClick(v: View) { when (v.id) { - R.id.button_done -> if (viewBinding.checkBoxGlobal.isChecked) { - viewModel.saveGlobally() - } else { - viewModel.save() - } - + R.id.button_done -> showSaveConfirmation() R.id.button_reset -> viewModel.reset() } } @@ -113,10 +113,23 @@ class ColorFilterConfigActivity : } } + fun showSaveConfirmation() { + MaterialAlertDialogBuilder(this) + .setTitle(R.string.apply) + .setMessage(R.string.color_correction_apply_text) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(R.string.this_manga) { _, _ -> + viewModel.saveGlobally() + }.setNeutralButton(R.string.globally) { _, _ -> + viewModel.save() + }.show() + } + private fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) { viewBinding.sliderBrightness.setValueRounded(readerColorFilter?.brightness ?: 0f) viewBinding.sliderContrast.setValueRounded(readerColorFilter?.contrast ?: 0f) viewBinding.switchInvert.setChecked(readerColorFilter?.isInverted ?: false, false) + viewBinding.switchGrayscale.setChecked(readerColorFilter?.isGrayscale ?: false, false) viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter() } @@ -138,6 +151,8 @@ class ColorFilterConfigActivity : private fun onLoadingChanged(isLoading: Boolean) { viewBinding.sliderContrast.isEnabled = !isLoading viewBinding.sliderBrightness.isEnabled = !isLoading + viewBinding.switchInvert.isEnabled = !isLoading + viewBinding.switchGrayscale.isEnabled = !isLoading viewBinding.buttonDone.isEnabled = !isLoading } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigBackPressedDispatcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigBackPressedDispatcher.kt index 97527946c..7a7a5480a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigBackPressedDispatcher.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigBackPressedDispatcher.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.reader.ui.colorfilter -import android.content.Context import android.content.DialogInterface import androidx.activity.OnBackPressedCallback import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -8,7 +7,7 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.util.ext.call class ColorFilterConfigBackPressedDispatcher( - private val context: Context, + private val activity: ColorFilterConfigActivity, private val viewModel: ColorFilterConfigViewModel, ) : OnBackPressedCallback(true), DialogInterface.OnClickListener { @@ -24,12 +23,12 @@ class ColorFilterConfigBackPressedDispatcher( when (which) { DialogInterface.BUTTON_NEGATIVE -> viewModel.onDismiss.call(Unit) DialogInterface.BUTTON_NEUTRAL -> dialog.dismiss() - DialogInterface.BUTTON_POSITIVE -> viewModel.save() + DialogInterface.BUTTON_POSITIVE -> activity.showSaveConfirmation() } } private fun showConfirmation() { - MaterialAlertDialogBuilder(context) + MaterialAlertDialogBuilder(activity) .setTitle(R.string.color_correction) .setMessage(R.string.text_unsaved_changes_prompt) .setNegativeButton(R.string.discard, this) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigViewModel.kt index e41e19045..0d38b4738 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigViewModel.kt @@ -44,33 +44,19 @@ class ColorFilterConfigViewModel @Inject constructor( } fun setBrightness(brightness: Float) { - val cf = colorFilter.value - colorFilter.value = ReaderColorFilter( - brightness = brightness, - contrast = cf?.contrast ?: 0f, - isInverted = cf?.isInverted ?: false, - ).takeUnless { it.isEmpty } + updateColorFilter { it.copy(brightness = brightness) } } fun setContrast(contrast: Float) { - val cf = colorFilter.value - colorFilter.value = ReaderColorFilter( - brightness = cf?.brightness ?: 0f, - contrast = contrast, - isInverted = cf?.isInverted ?: false, - ).takeUnless { it.isEmpty } + updateColorFilter { it.copy(contrast = contrast) } } fun setInversion(invert: Boolean) { - val cf = colorFilter.value - if (invert == cf?.isInverted) { - return - } - colorFilter.value = ReaderColorFilter( - brightness = cf?.brightness ?: 0f, - contrast = cf?.contrast ?: 0f, - isInverted = invert, - ).takeUnless { it.isEmpty } + updateColorFilter { it.copy(isInverted = invert) } + } + + fun setGrayscale(grayscale: Boolean) { + updateColorFilter { it.copy(isGrayscale = grayscale) } } fun reset() { @@ -85,7 +71,18 @@ class ColorFilterConfigViewModel @Inject constructor( } fun saveGlobally() { - settings.readerColorFilter = colorFilter.value - onDismiss.call(Unit) + launchLoadingJob(Dispatchers.Default) { + settings.readerColorFilter = colorFilter.value + if (mangaDataRepository.getColorFilter(manga.id) != null) { + mangaDataRepository.saveColorFilter(manga, colorFilter.value) + } + onDismiss.call(Unit) + } + } + + private inline fun updateColorFilter(block: (ReaderColorFilter) -> ReaderColorFilter) { + colorFilter.value = block( + colorFilter.value ?: ReaderColorFilter.EMPTY, + ).takeUnless { it.isEmpty } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt index 0cc10934e..60708cfcc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/config/ReaderSettings.kt @@ -102,10 +102,10 @@ class ReaderSettings( AppSettings.KEY_READER_BACKGROUND, AppSettings.KEY_32BIT_COLOR, AppSettings.KEY_READER_OPTIMIZE, - AppSettings.KEY_CF_ENABLED, AppSettings.KEY_CF_CONTRAST, AppSettings.KEY_CF_BRIGHTNESS, AppSettings.KEY_CF_INVERTED, + AppSettings.KEY_CF_GRAYSCALE, ) override suspend fun emit(value: ReaderColorFilter?) { diff --git a/app/src/main/res/layout-w600dp-land/activity_color_filter.xml b/app/src/main/res/layout-w600dp-land/activity_color_filter.xml index 88060cdeb..88bbbdb14 100644 --- a/app/src/main/res/layout-w600dp-land/activity_color_filter.xml +++ b/app/src/main/res/layout-w600dp-land/activity_color_filter.xml @@ -127,6 +127,18 @@ app:layout_constraintStart_toEndOf="@id/guideline_vertical" app:layout_constraintTop_toTopOf="parent" /> + + + app:layout_constraintTop_toBottomOf="@id/switch_grayscale" /> - - - - - diff --git a/app/src/main/res/layout/activity_color_filter.xml b/app/src/main/res/layout/activity_color_filter.xml index 21850f5ee..c38c88891 100644 --- a/app/src/main/res/layout/activity_color_filter.xml +++ b/app/src/main/res/layout/activity_color_filter.xml @@ -111,6 +111,17 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/imageView_before" /> + + + app:layout_constraintTop_toBottomOf="@id/switch_grayscale" /> - - - -