Color filter support in reader
This commit is contained in:
@@ -10,6 +10,9 @@ import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@@ -23,6 +26,7 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||
|
||||
private const val MIN_WEBTOON_RATIO = 2
|
||||
|
||||
@@ -31,15 +35,22 @@ class MangaDataRepository @Inject constructor(
|
||||
private val db: MangaDatabase,
|
||||
) {
|
||||
|
||||
suspend fun savePreferences(manga: Manga, mode: ReaderMode) {
|
||||
val tags = manga.tags.toEntities()
|
||||
suspend fun saveReaderMode(manga: Manga, mode: ReaderMode) {
|
||||
db.withTransaction {
|
||||
db.tagsDao.upsert(tags)
|
||||
db.mangaDao.upsert(manga.toEntity(), tags)
|
||||
storeManga(manga)
|
||||
val entity = db.preferencesDao.find(manga.id) ?: newEntity(manga.id)
|
||||
db.preferencesDao.upsert(entity.copy(mode = mode.id))
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveColorFilter(manga: Manga, colorFilter: ReaderColorFilter?) {
|
||||
db.withTransaction {
|
||||
storeManga(manga)
|
||||
val entity = db.preferencesDao.find(manga.id) ?: newEntity(manga.id)
|
||||
db.preferencesDao.upsert(
|
||||
MangaPrefsEntity(
|
||||
mangaId = manga.id,
|
||||
mode = mode.id,
|
||||
entity.copy(
|
||||
cfBrightness = colorFilter?.brightness ?: 0f,
|
||||
cfContrast = colorFilter?.contrast ?: 0f,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -49,6 +60,16 @@ class MangaDataRepository @Inject constructor(
|
||||
return db.preferencesDao.find(mangaId)?.let { ReaderMode.valueOf(it.mode) }
|
||||
}
|
||||
|
||||
suspend fun getColorFilter(mangaId: Long): ReaderColorFilter? {
|
||||
return db.preferencesDao.find(mangaId)?.getColorFilterOrNull()
|
||||
}
|
||||
|
||||
fun observeColorFilter(mangaId: Long): Flow<ReaderColorFilter?> {
|
||||
return db.preferencesDao.observe(mangaId)
|
||||
.map { it?.getColorFilterOrNull() }
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
|
||||
suspend fun findMangaById(mangaId: Long): Manga? {
|
||||
return db.mangaDao.find(mangaId)?.toManga()
|
||||
}
|
||||
@@ -71,6 +92,14 @@ class MangaDataRepository @Inject constructor(
|
||||
return db.tagsDao.findTags(source.name).toMangaTags()
|
||||
}
|
||||
|
||||
private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? {
|
||||
return if (cfBrightness != 0f || cfContrast != 0f) {
|
||||
ReaderColorFilter(cfBrightness, cfContrast)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic determine type of manga by page size
|
||||
* @return ReaderMode.WEBTOON if page is wide
|
||||
@@ -104,6 +133,13 @@ class MangaDataRepository @Inject constructor(
|
||||
return size.width * MIN_WEBTOON_RATIO < size.height
|
||||
}
|
||||
|
||||
private fun newEntity(mangaId: Long) = MangaPrefsEntity(
|
||||
mangaId = mangaId,
|
||||
mode = -1,
|
||||
cfBrightness = 0f,
|
||||
cfContrast = 0f,
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
suspend fun getImageMimeType(file: File): String? = runInterruptible(Dispatchers.IO) {
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.koitharu.kotatsu.tracker.data.TrackLogEntity
|
||||
import org.koitharu.kotatsu.tracker.data.TracksDao
|
||||
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||
|
||||
const val DATABASE_VERSION = 14
|
||||
const val DATABASE_VERSION = 15
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
@@ -86,6 +86,7 @@ val databaseMigrations: Array<Migration>
|
||||
Migration11To12(),
|
||||
Migration12To13(),
|
||||
Migration13To14(),
|
||||
Migration14To15(),
|
||||
)
|
||||
|
||||
fun MangaDatabase(context: Context): MangaDatabase = Room
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.core.db.dao
|
||||
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
|
||||
|
||||
@Dao
|
||||
@@ -9,6 +10,9 @@ abstract class PreferencesDao {
|
||||
@Query("SELECT * FROM preferences WHERE manga_id = :mangaId")
|
||||
abstract suspend fun find(mangaId: Long): MangaPrefsEntity?
|
||||
|
||||
@Query("SELECT * FROM preferences WHERE manga_id = :mangaId")
|
||||
abstract fun observe(mangaId: Long): Flow<MangaPrefsEntity?>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract suspend fun insert(pref: MangaPrefsEntity): Long
|
||||
|
||||
@@ -21,4 +25,4 @@ abstract class PreferencesDao {
|
||||
insert(pref)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,15 @@ import androidx.room.PrimaryKey
|
||||
entity = MangaEntity::class,
|
||||
parentColumns = ["manga_id"],
|
||||
childColumns = ["manga_id"],
|
||||
onDelete = ForeignKey.CASCADE
|
||||
)
|
||||
]
|
||||
onDelete = ForeignKey.CASCADE,
|
||||
),
|
||||
],
|
||||
)
|
||||
class MangaPrefsEntity(
|
||||
data class MangaPrefsEntity(
|
||||
@PrimaryKey(autoGenerate = false)
|
||||
@ColumnInfo(name = "manga_id") val mangaId: Long,
|
||||
@ColumnInfo(name = "mode") val mode: Int
|
||||
)
|
||||
@ColumnInfo(name = "manga_id")
|
||||
val mangaId: Long,
|
||||
@ColumnInfo(name = "mode") val mode: Int,
|
||||
@ColumnInfo(name = "cf_brightness") val cfBrightness: Float,
|
||||
@ColumnInfo(name = "cf_contrast") val cfContrast: Float,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.koitharu.kotatsu.core.db.migrations
|
||||
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
|
||||
class Migration14To15 : Migration(14, 15) {
|
||||
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE preferences ADD COLUMN `cf_brightness` REAL NOT NULL DEFAULT 0")
|
||||
database.execSQL("ALTER TABLE preferences ADD COLUMN `cf_contrast` REAL NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
|
||||
|
||||
@HiltViewModel
|
||||
@@ -32,7 +33,7 @@ class MainViewModel @Inject constructor(
|
||||
|
||||
val isResumeEnabled = historyRepository
|
||||
.observeHasItems()
|
||||
.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, false)
|
||||
.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, false)
|
||||
|
||||
val counters = combine(
|
||||
appUpdateRepository.observeAvailableUpdate(),
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.koitharu.kotatsu.reader.domain
|
||||
|
||||
import android.graphics.ColorMatrix
|
||||
import android.graphics.ColorMatrixColorFilter
|
||||
|
||||
class ReaderColorFilter(
|
||||
val brightness: Float,
|
||||
val contrast: Float,
|
||||
) {
|
||||
|
||||
val isEmpty: Boolean
|
||||
get() = brightness == 0f && contrast == 0f
|
||||
|
||||
fun toColorFilter(): ColorMatrixColorFilter {
|
||||
val cm = ColorMatrix()
|
||||
val scale = brightness + 1f
|
||||
cm.setScale(scale, scale, scale, 1f)
|
||||
cm.setContrast(contrast)
|
||||
return ColorMatrixColorFilter(cm)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ReaderColorFilter
|
||||
|
||||
if (brightness != other.brightness) return false
|
||||
if (contrast != other.contrast) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = brightness.hashCode()
|
||||
result = 31 * result + contrast.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun ColorMatrix.setContrast(contrast: Float) {
|
||||
val scale = contrast + 1f
|
||||
val translate = (-.5f * scale + .5f) * 255f
|
||||
val array = floatArrayOf(
|
||||
scale, 0f, 0f, 0f, translate,
|
||||
0f, scale, 0f, 0f, translate,
|
||||
0f, 0f, scale, 0f, translate,
|
||||
0f, 0f, 0f, 1f, 0f,
|
||||
)
|
||||
val matrix = ColorMatrix(array)
|
||||
postConcat(matrix)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.reader.data.filterChapters
|
||||
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.utils.asFlowLiveData
|
||||
@@ -86,6 +87,14 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
valueProducer = { isReaderBarEnabled },
|
||||
)
|
||||
|
||||
val readerSettings = ReaderSettings(
|
||||
parentScope = viewModelScope,
|
||||
settings = settings,
|
||||
colorFilterFlow = mangaData.flatMapLatest {
|
||||
if (it == null) flowOf(null) else dataRepository.observeColorFilter(it.id)
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null),
|
||||
)
|
||||
|
||||
val isScreenshotsBlockEnabled = combine(
|
||||
mangaData,
|
||||
settings.observeAsFlow(AppSettings.KEY_SCREENSHOTS_POLICY) { screenshotsPolicy },
|
||||
@@ -94,8 +103,6 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
(policy == ScreenshotsPolicy.BLOCK_NSFW && manga != null && manga.isNsfw)
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, false)
|
||||
|
||||
val onZoomChanged = SingleLiveEvent<Unit>()
|
||||
|
||||
val isBookmarkAdded: LiveData<Boolean> = currentState.flatMapLatest { state ->
|
||||
val manga = mangaData.value
|
||||
if (state == null || manga == null) {
|
||||
@@ -108,7 +115,6 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
|
||||
init {
|
||||
loadImpl()
|
||||
subscribeToSettings()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
@@ -124,7 +130,7 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
fun switchMode(newMode: ReaderMode) {
|
||||
launchJob {
|
||||
val manga = checkNotNull(mangaData.value)
|
||||
dataRepository.savePreferences(
|
||||
dataRepository.saveReaderMode(
|
||||
manga = manga,
|
||||
mode = newMode,
|
||||
)
|
||||
@@ -300,13 +306,6 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToSettings() {
|
||||
settings.observe()
|
||||
.onEach { key ->
|
||||
if (key == AppSettings.KEY_ZOOM_MODE) onZoomChanged.postCall(Unit)
|
||||
}.launchIn(viewModelScope + Dispatchers.Default)
|
||||
}
|
||||
|
||||
private fun <T> List<T>.trySublist(fromIndex: Int, toIndex: Int): List<T> {
|
||||
val fromIndexBounded = fromIndex.coerceAtMost(lastIndex)
|
||||
val toIndexBounded = toIndex.coerceIn(fromIndexBounded, lastIndex)
|
||||
@@ -331,7 +330,7 @@ class ReaderViewModel @AssistedInject constructor(
|
||||
val isWebtoon = dataRepository.determineMangaIsWebtoon(repo, pages)
|
||||
if (isWebtoon) ReaderMode.WEBTOON else defaultMode
|
||||
}.onSuccess {
|
||||
dataRepository.savePreferences(manga, it)
|
||||
dataRepository.saveReaderMode(manga, it)
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}.getOrDefault(defaultMode)
|
||||
|
||||
@@ -2,40 +2,54 @@ package org.koitharu.kotatsu.reader.ui.colorfilter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.LightingColorFilter
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil.size.ViewSizeResolver
|
||||
import com.google.android.material.R as materialR
|
||||
import com.google.android.material.slider.LabelFormatter
|
||||
import com.google.android.material.slider.Slider
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPages
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.roundToInt
|
||||
import com.google.android.material.R as materialR
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.base.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaPages
|
||||
import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.format
|
||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||
import org.koitharu.kotatsu.utils.ext.assistedViewModels
|
||||
import org.koitharu.kotatsu.utils.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
|
||||
import org.koitharu.kotatsu.utils.ext.referer
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ColorFilterConfigActivity : BaseActivity<ActivityColorFilterBinding>(), Slider.OnChangeListener {
|
||||
class ColorFilterConfigActivity :
|
||||
BaseActivity<ActivityColorFilterBinding>(),
|
||||
Slider.OnChangeListener,
|
||||
View.OnClickListener {
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
@Inject
|
||||
lateinit var mangaRepositoryFacotry: MangaRepository.Factory
|
||||
lateinit var viewModelFactory: ColorFilterConfigViewModel.Factory
|
||||
|
||||
private val viewModel: ColorFilterConfigViewModel by assistedViewModels {
|
||||
viewModelFactory.create(
|
||||
manga = checkNotNull(intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga),
|
||||
page = checkNotNull(intent.getParcelableExtraCompat<ParcelableMangaPages>(EXTRA_PAGES)?.pages?.firstOrNull()),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -44,14 +58,38 @@ class ColorFilterConfigActivity : BaseActivity<ActivityColorFilterBinding>(), Sl
|
||||
setDisplayHomeAsUpEnabled(true)
|
||||
setHomeAsUpIndicator(materialR.drawable.abc_ic_clear_material)
|
||||
}
|
||||
binding.sliderLightness.addOnChangeListener(this)
|
||||
binding.sliderSaturation.addOnChangeListener(this)
|
||||
initPreview()
|
||||
updateFilter()
|
||||
binding.sliderBrightness.addOnChangeListener(this)
|
||||
binding.sliderContrast.addOnChangeListener(this)
|
||||
val formatter = PercentLabelFormatter(resources)
|
||||
binding.sliderContrast.setLabelFormatter(formatter)
|
||||
binding.sliderBrightness.setLabelFormatter(formatter)
|
||||
binding.buttonDone.setOnClickListener(this)
|
||||
binding.buttonReset.setOnClickListener(this)
|
||||
|
||||
onBackPressedDispatcher.addCallback(ColorFilterConfigBackPressedDispatcher(this, viewModel))
|
||||
|
||||
viewModel.colorFilter.observe(this, this::onColorFilterChanged)
|
||||
viewModel.isLoading.observe(this, this::onLoadingChanged)
|
||||
viewModel.preview.observe(this, this::onPreviewChanged)
|
||||
viewModel.onDismiss.observe(this) {
|
||||
finishAfterTransition()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
|
||||
updateFilter()
|
||||
if (fromUser) {
|
||||
when (slider.id) {
|
||||
R.id.slider_brightness -> viewModel.setBrightness(value)
|
||||
R.id.slider_contrast -> viewModel.setContrast(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_done -> viewModel.save()
|
||||
R.id.button_reset -> viewModel.reset()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWindowInsetsChanged(insets: Insets) {
|
||||
@@ -67,42 +105,49 @@ class ColorFilterConfigActivity : BaseActivity<ActivityColorFilterBinding>(), Sl
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateFilter() {
|
||||
|
||||
fun Int.toColor() = Color.rgb(this, this, this)
|
||||
|
||||
val cf = LightingColorFilter(
|
||||
binding.sliderSaturation.value.roundToInt().toColor(),
|
||||
binding.sliderLightness.value.roundToInt().toColor(),
|
||||
)
|
||||
binding.imageViewAfter.colorFilter = cf
|
||||
private fun onColorFilterChanged(readerColorFilter: ReaderColorFilter?) {
|
||||
binding.sliderBrightness.value = readerColorFilter?.brightness ?: 0f
|
||||
binding.sliderContrast.value = readerColorFilter?.contrast ?: 0f
|
||||
binding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter()
|
||||
}
|
||||
|
||||
private fun initPreview() {
|
||||
val page = intent?.getParcelableExtra<ParcelableMangaPages>(EXTRA_PAGES)?.pages?.firstOrNull()
|
||||
if (page == null) {
|
||||
finishAfterTransition()
|
||||
return
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val repository = mangaRepositoryFacotry.create(page.source)
|
||||
val url = repository.getPageUrl(page)
|
||||
ImageRequest.Builder(this@ColorFilterConfigActivity)
|
||||
.data(url)
|
||||
.referer(page.referer)
|
||||
.scale(Scale.FILL)
|
||||
.size(ViewSizeResolver(binding.imageViewBefore))
|
||||
.allowRgb565(false)
|
||||
.target(ShadowViewTarget(binding.imageViewBefore, binding.imageViewAfter))
|
||||
.enqueueWith(coil)
|
||||
private fun onPreviewChanged(preview: MangaPage?) {
|
||||
if (preview == null) return
|
||||
ImageRequest.Builder(this@ColorFilterConfigActivity)
|
||||
.data(preview.url)
|
||||
.referer(preview.referer)
|
||||
.scale(Scale.FILL)
|
||||
.error(R.drawable.ic_error_placeholder)
|
||||
.size(ViewSizeResolver(binding.imageViewBefore))
|
||||
.allowRgb565(false)
|
||||
.target(ShadowViewTarget(binding.imageViewBefore, binding.imageViewAfter))
|
||||
.enqueueWith(coil)
|
||||
}
|
||||
|
||||
private fun onLoadingChanged(isLoading: Boolean) {
|
||||
binding.sliderContrast.isEnabled = !isLoading
|
||||
binding.sliderBrightness.isEnabled = !isLoading
|
||||
binding.buttonDone.isEnabled = !isLoading
|
||||
}
|
||||
|
||||
private class PercentLabelFormatter(resources: Resources) : LabelFormatter {
|
||||
|
||||
private val pattern = resources.getString(R.string.percent_string_pattern)
|
||||
|
||||
override fun getFormattedValue(value: Float): String {
|
||||
val percent = ((value + 1f) * 100).format(0)
|
||||
return pattern.format(percent)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_PAGES = "pages"
|
||||
private const val EXTRA_MANGA = "manga_id"
|
||||
|
||||
fun newIntent(context: Context, page: MangaPage) = Intent(context, ColorFilterConfigActivity::class.java)
|
||||
.putExtra(EXTRA_PAGES, ParcelableMangaPages(listOf(page)))
|
||||
fun newIntent(context: Context, manga: Manga, page: MangaPage) =
|
||||
Intent(context, ColorFilterConfigActivity::class.java)
|
||||
.putExtra(EXTRA_MANGA, ParcelableManga(manga, false))
|
||||
.putExtra(EXTRA_PAGES, ParcelableMangaPages(listOf(page)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
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
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
class ColorFilterConfigBackPressedDispatcher(
|
||||
private val context: Context,
|
||||
private val viewModel: ColorFilterConfigViewModel,
|
||||
) : OnBackPressedCallback(true), DialogInterface.OnClickListener {
|
||||
|
||||
override fun handleOnBackPressed() {
|
||||
if (viewModel.isChanged) {
|
||||
showConfirmation()
|
||||
} else {
|
||||
viewModel.onDismiss.call(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(dialog: DialogInterface, which: Int) {
|
||||
when (which) {
|
||||
DialogInterface.BUTTON_NEGATIVE -> viewModel.onDismiss.call(Unit)
|
||||
DialogInterface.BUTTON_NEUTRAL -> dialog.dismiss()
|
||||
DialogInterface.BUTTON_POSITIVE -> viewModel.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showConfirmation() {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.color_correction)
|
||||
.setMessage(R.string.text_unsaved_changes_prompt)
|
||||
.setNegativeButton(R.string.discard, this)
|
||||
.setNeutralButton(android.R.string.cancel, this)
|
||||
.setPositiveButton(R.string.save, this)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package org.koitharu.kotatsu.reader.ui.colorfilter
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import org.koitharu.kotatsu.base.domain.MangaDataRepository
|
||||
import org.koitharu.kotatsu.base.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||
import org.koitharu.kotatsu.utils.SingleLiveEvent
|
||||
|
||||
class ColorFilterConfigViewModel @AssistedInject constructor(
|
||||
@Assisted private val manga: Manga,
|
||||
@Assisted page: MangaPage,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val mangaDataRepository: MangaDataRepository,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private var initialColorFilter: ReaderColorFilter? = null
|
||||
val colorFilter = MutableLiveData<ReaderColorFilter?>(null)
|
||||
val onDismiss = SingleLiveEvent<Unit>()
|
||||
val preview = MutableLiveData<MangaPage?>(null)
|
||||
|
||||
val isChanged: Boolean
|
||||
get() = colorFilter.value != initialColorFilter
|
||||
|
||||
init {
|
||||
launchLoadingJob {
|
||||
initialColorFilter = mangaDataRepository.getColorFilter(manga.id)
|
||||
colorFilter.value = initialColorFilter
|
||||
}
|
||||
launchLoadingJob {
|
||||
val repository = mangaRepositoryFactory.create(page.source)
|
||||
val url = repository.getPageUrl(page)
|
||||
preview.value = MangaPage(
|
||||
id = page.id,
|
||||
url = url,
|
||||
referer = page.referer,
|
||||
preview = page.preview,
|
||||
source = page.source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setBrightness(brightness: Float) {
|
||||
val cf = colorFilter.value
|
||||
colorFilter.value = ReaderColorFilter(brightness, cf?.contrast ?: 0f).takeUnless { it.isEmpty }
|
||||
}
|
||||
|
||||
fun setContrast(contrast: Float) {
|
||||
val cf = colorFilter.value
|
||||
colorFilter.value = ReaderColorFilter(cf?.brightness ?: 0f, contrast).takeUnless { it.isEmpty }
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
colorFilter.value = null
|
||||
}
|
||||
|
||||
fun save() {
|
||||
launchLoadingJob {
|
||||
mangaDataRepository.saveColorFilter(manga, colorFilter.value)
|
||||
onDismiss.call(Unit)
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
||||
fun create(manga: Manga, page: MangaPage): ColorFilterConfigViewModel
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,8 @@ class ReaderConfigBottomSheet :
|
||||
}
|
||||
R.id.button_color_filter -> {
|
||||
val page = viewModel.getCurrentPage() ?: return
|
||||
startActivity(ColorFilterConfigActivity.newIntent(v.context, page))
|
||||
val manga = viewModel.manga ?: return
|
||||
startActivity(ColorFilterConfigActivity.newIntent(v.context, manga, page))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.koitharu.kotatsu.reader.ui.config
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
|
||||
|
||||
class ReaderSettings(
|
||||
private val parentScope: CoroutineScope,
|
||||
private val settings: AppSettings,
|
||||
private val colorFilterFlow: StateFlow<ReaderColorFilter?>,
|
||||
) : MediatorLiveData<ReaderSettings>() {
|
||||
|
||||
private val internalObserver = InternalObserver()
|
||||
private var collectJob: Job? = null
|
||||
|
||||
val zoomMode: ZoomMode
|
||||
get() = settings.zoomMode
|
||||
|
||||
val colorFilter: ReaderColorFilter?
|
||||
get() = colorFilterFlow.value
|
||||
|
||||
val isPagesNumbersEnabled: Boolean
|
||||
get() = settings.isPagesNumbersEnabled
|
||||
|
||||
override fun onInactive() {
|
||||
super.onInactive()
|
||||
settings.unsubscribe(internalObserver)
|
||||
collectJob?.cancel()
|
||||
collectJob = null
|
||||
}
|
||||
|
||||
override fun onActive() {
|
||||
super.onActive()
|
||||
settings.subscribe(internalObserver)
|
||||
collectJob?.cancel()
|
||||
collectJob = parentScope.launch {
|
||||
colorFilterFlow.collect(internalObserver)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getValue() = this
|
||||
|
||||
private fun notifyChanged() {
|
||||
value = value
|
||||
}
|
||||
|
||||
private inner class InternalObserver :
|
||||
FlowCollector<ReaderColorFilter?>,
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
override suspend fun emit(value: ReaderColorFilter?) {
|
||||
withContext(Dispatchers.Main.immediate) {
|
||||
notifyChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||
if (key == AppSettings.KEY_ZOOM_MODE || key == AppSettings.KEY_PAGES_NUMBERS) {
|
||||
notifyChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,15 @@ import androidx.annotation.CallSuper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.LayoutPageInfoBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
|
||||
abstract class BasePageHolder<B : ViewBinding>(
|
||||
protected val binding: B,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : RecyclerView.ViewHolder(binding.root), PageHolderDelegate.Callback {
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
@@ -37,8 +37,16 @@ abstract class BasePageHolder<B : ViewBinding>(
|
||||
|
||||
protected abstract fun onBind(data: ReaderPage)
|
||||
|
||||
fun onAttachedToWindow() {
|
||||
delegate.onAttachedToWindow()
|
||||
}
|
||||
|
||||
fun onDetachedFromWindow() {
|
||||
delegate.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
open fun onRecycled() {
|
||||
delegate.onRecycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.utils.ext.resetTransformations
|
||||
|
||||
@Suppress("LeakingThis")
|
||||
abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
|
||||
private val loader: PageLoader,
|
||||
private val settings: AppSettings,
|
||||
private val readerSettings: ReaderSettings,
|
||||
private val exceptionResolver: ExceptionResolver,
|
||||
) : RecyclerView.Adapter<H>() {
|
||||
|
||||
@@ -35,6 +35,16 @@ abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
|
||||
super.onViewRecycled(holder)
|
||||
}
|
||||
|
||||
override fun onViewAttachedToWindow(holder: H) {
|
||||
super.onViewAttachedToWindow(holder)
|
||||
holder.onAttachedToWindow()
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: H) {
|
||||
holder.onDetachedFromWindow()
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
}
|
||||
|
||||
open fun getItem(position: Int): ReaderPage = differ.currentList[position]
|
||||
|
||||
open fun getItemOrNull(position: Int) = differ.currentList.getOrNull(position)
|
||||
@@ -46,7 +56,7 @@ abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
|
||||
final override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int,
|
||||
): H = onCreateViewHolder(parent, loader, settings, exceptionResolver)
|
||||
): H = onCreateViewHolder(parent, loader, readerSettings, exceptionResolver)
|
||||
|
||||
suspend fun setItems(items: List<ReaderPage>) = suspendCoroutine<Unit> { cont ->
|
||||
differ.submitList(items) {
|
||||
@@ -57,7 +67,7 @@ abstract class BaseReaderAdapter<H : BasePageHolder<*>>(
|
||||
protected abstract fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
): H
|
||||
|
||||
|
||||
@@ -2,26 +2,26 @@ package org.koitharu.kotatsu.reader.ui.pager
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Observer
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
|
||||
class PageHolderDelegate(
|
||||
private val loader: PageLoader,
|
||||
private val settings: AppSettings,
|
||||
private val readerSettings: ReaderSettings,
|
||||
private val callback: Callback,
|
||||
private val exceptionResolver: ExceptionResolver
|
||||
) : SubsamplingScaleImageView.DefaultOnImageEventListener() {
|
||||
private val exceptionResolver: ExceptionResolver,
|
||||
) : SubsamplingScaleImageView.DefaultOnImageEventListener(), Observer<ReaderSettings> {
|
||||
|
||||
private val scope = loader.loaderScope + Dispatchers.Main.immediate
|
||||
private var state = State.EMPTY
|
||||
@@ -49,6 +49,14 @@ class PageHolderDelegate(
|
||||
}
|
||||
}
|
||||
|
||||
fun onAttachedToWindow() {
|
||||
readerSettings.observeForever(this)
|
||||
}
|
||||
|
||||
fun onDetachedFromWindow() {
|
||||
readerSettings.removeObserver(this)
|
||||
}
|
||||
|
||||
fun onRecycle() {
|
||||
state = State.EMPTY
|
||||
file = null
|
||||
@@ -59,7 +67,7 @@ class PageHolderDelegate(
|
||||
override fun onReady() {
|
||||
state = State.SHOWING
|
||||
error = null
|
||||
callback.onImageShowing(settings.zoomMode)
|
||||
callback.onImageShowing(readerSettings)
|
||||
}
|
||||
|
||||
override fun onImageLoaded() {
|
||||
@@ -79,6 +87,12 @@ class PageHolderDelegate(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChanged(t: ReaderSettings?) {
|
||||
if (state == State.SHOWN) {
|
||||
callback.onImageShowing(readerSettings)
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryConvert(file: File, e: Exception) {
|
||||
val prevJob = job
|
||||
job = scope.launch {
|
||||
@@ -134,10 +148,10 @@ class PageHolderDelegate(
|
||||
|
||||
fun onImageReady(uri: Uri)
|
||||
|
||||
fun onImageShowing(zoom: ZoomMode)
|
||||
fun onImageShowing(settings: ReaderSettings)
|
||||
|
||||
fun onImageShown()
|
||||
|
||||
fun onProgressChanged(progress: Int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,16 @@ import android.widget.FrameLayout
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.ItemPageBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.standard.PageHolder
|
||||
|
||||
class ReversedPageHolder(
|
||||
binding: ItemPageBinding,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : PageHolder(binding, loader, settings, exceptionResolver) {
|
||||
|
||||
init {
|
||||
@@ -23,13 +23,14 @@ class ReversedPageHolder(
|
||||
.gravity = Gravity.START or Gravity.BOTTOM
|
||||
}
|
||||
|
||||
override fun onImageShowing(zoom: ZoomMode) {
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
with(binding.ssiv) {
|
||||
maxScale = 2f * maxOf(
|
||||
width / sWidth.toFloat(),
|
||||
height / sHeight.toFloat()
|
||||
height / sHeight.toFloat(),
|
||||
)
|
||||
when (zoom) {
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
when (settings.zoomMode) {
|
||||
ZoomMode.FIT_CENTER -> {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
|
||||
resetScaleAndCenter()
|
||||
@@ -39,7 +40,7 @@ class ReversedPageHolder(
|
||||
minScale = height / sHeight.toFloat()
|
||||
setScaleAndCenter(
|
||||
minScale,
|
||||
PointF(sWidth.toFloat(), sHeight / 2f)
|
||||
PointF(sWidth.toFloat(), sHeight / 2f),
|
||||
)
|
||||
}
|
||||
ZoomMode.FIT_WIDTH -> {
|
||||
@@ -47,17 +48,17 @@ class ReversedPageHolder(
|
||||
minScale = width / sWidth.toFloat()
|
||||
setScaleAndCenter(
|
||||
minScale,
|
||||
PointF(sWidth / 2f, 0f)
|
||||
PointF(sWidth / 2f, 0f),
|
||||
)
|
||||
}
|
||||
ZoomMode.KEEP_START -> {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
|
||||
setScaleAndCenter(
|
||||
maxScale,
|
||||
PointF(sWidth.toFloat(), 0f)
|
||||
PointF(sWidth.toFloat(), 0f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,26 +3,26 @@ package org.koitharu.kotatsu.reader.ui.pager.reversed
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.ItemPageBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
|
||||
|
||||
class ReversedPagesAdapter(
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : BaseReaderAdapter<ReversedPageHolder>(loader, settings, exceptionResolver) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) = ReversedPageHolder(
|
||||
binding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
loader = loader,
|
||||
settings = settings,
|
||||
exceptionResolver = exceptionResolver
|
||||
exceptionResolver = exceptionResolver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlinx.coroutines.async
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
@@ -25,9 +23,6 @@ import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
@AndroidEntryPoint
|
||||
class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private var pagerAdapter: ReversedPagesAdapter? = null
|
||||
|
||||
override fun onInflateView(
|
||||
@@ -38,7 +33,7 @@ class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
pagerAdapter = ReversedPagesAdapter(viewModel.pageLoader, settings, exceptionResolver)
|
||||
pagerAdapter = ReversedPagesAdapter(viewModel.pageLoader, viewModel.readerSettings, exceptionResolver)
|
||||
with(binding.pager) {
|
||||
adapter = pagerAdapter
|
||||
offscreenPageLimit = 2
|
||||
@@ -54,9 +49,6 @@ class ReversedReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModel.onZoomChanged.observe(viewLifecycleOwner) {
|
||||
pagerAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -12,9 +12,9 @@ import kotlinx.coroutines.asExecutor
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.ItemPageBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import org.koitharu.kotatsu.utils.ext.*
|
||||
@@ -22,7 +22,7 @@ import org.koitharu.kotatsu.utils.ext.*
|
||||
open class PageHolder(
|
||||
binding: ItemPageBinding,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : BasePageHolder<ItemPageBinding>(binding, loader, settings, exceptionResolver),
|
||||
View.OnClickListener {
|
||||
@@ -66,12 +66,13 @@ open class PageHolder(
|
||||
binding.ssiv.setImage(ImageSource.uri(uri))
|
||||
}
|
||||
|
||||
override fun onImageShowing(zoom: ZoomMode) {
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
binding.ssiv.maxScale = 2f * maxOf(
|
||||
binding.ssiv.width / binding.ssiv.sWidth.toFloat(),
|
||||
binding.ssiv.height / binding.ssiv.sHeight.toFloat(),
|
||||
)
|
||||
when (zoom) {
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
when (settings.zoomMode) {
|
||||
ZoomMode.FIT_CENTER -> {
|
||||
binding.ssiv.setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE)
|
||||
binding.ssiv.resetScaleAndCenter()
|
||||
|
||||
@@ -7,10 +7,8 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlinx.coroutines.async
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.FragmentReaderStandardBinding
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
@@ -24,9 +22,6 @@ import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
@AndroidEntryPoint
|
||||
class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private var pagesAdapter: PagesAdapter? = null
|
||||
|
||||
override fun onInflateView(
|
||||
@@ -37,7 +32,7 @@ class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
pagesAdapter = PagesAdapter(viewModel.pageLoader, settings, exceptionResolver)
|
||||
pagesAdapter = PagesAdapter(viewModel.pageLoader, viewModel.readerSettings, exceptionResolver)
|
||||
with(binding.pager) {
|
||||
adapter = pagesAdapter
|
||||
offscreenPageLimit = 2
|
||||
@@ -53,9 +48,6 @@ class PagerReaderFragment : BaseReader<FragmentReaderStandardBinding>() {
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModel.onZoomChanged.observe(viewLifecycleOwner) {
|
||||
pagesAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
||||
@@ -3,26 +3,26 @@ package org.koitharu.kotatsu.reader.ui.pager.standard
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.ItemPageBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
|
||||
|
||||
class PagesAdapter(
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : BaseReaderAdapter<PageHolder>(loader, settings, exceptionResolver) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) = PageHolder(
|
||||
binding = ItemPageBinding.inflate(LayoutInflater.from(parent.context), parent, false),
|
||||
loader = loader,
|
||||
settings = settings,
|
||||
exceptionResolver = exceptionResolver
|
||||
exceptionResolver = exceptionResolver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,27 +6,28 @@ import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReaderAdapter
|
||||
|
||||
class WebtoonAdapter(
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : BaseReaderAdapter<WebtoonHolder>(loader, settings, exceptionResolver) {
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
exceptionResolver: ExceptionResolver
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) = WebtoonHolder(
|
||||
binding = ItemPageWebtoonBinding.inflate(
|
||||
LayoutInflater.from(parent.context),
|
||||
parent,
|
||||
false
|
||||
false,
|
||||
),
|
||||
loader = loader,
|
||||
settings = settings,
|
||||
exceptionResolver = exceptionResolver
|
||||
exceptionResolver = exceptionResolver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,9 @@ import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.ExceptionResolver
|
||||
import org.koitharu.kotatsu.core.model.ZoomMode
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.ItemPageWebtoonBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import org.koitharu.kotatsu.reader.ui.config.ReaderSettings
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BasePageHolder
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import org.koitharu.kotatsu.utils.GoneOnInvisibleListener
|
||||
@@ -19,7 +18,7 @@ import org.koitharu.kotatsu.utils.ext.*
|
||||
class WebtoonHolder(
|
||||
binding: ItemPageWebtoonBinding,
|
||||
loader: PageLoader,
|
||||
settings: AppSettings,
|
||||
settings: ReaderSettings,
|
||||
exceptionResolver: ExceptionResolver,
|
||||
) : BasePageHolder<ItemPageWebtoonBinding>(binding, loader, settings, exceptionResolver),
|
||||
View.OnClickListener {
|
||||
@@ -60,7 +59,8 @@ class WebtoonHolder(
|
||||
binding.ssiv.setImage(ImageSource.uri(uri))
|
||||
}
|
||||
|
||||
override fun onImageShowing(zoom: ZoomMode) {
|
||||
override fun onImageShowing(settings: ReaderSettings) {
|
||||
binding.ssiv.colorFilter = settings.colorFilter?.toColorFilter()
|
||||
with(binding.ssiv) {
|
||||
setMinimumScaleType(SubsamplingScaleImageView.SCALE_TYPE_CUSTOM)
|
||||
minScale = width / sWidth.toFloat()
|
||||
@@ -70,7 +70,7 @@ class WebtoonHolder(
|
||||
scrollToRestore != 0 -> scrollToRestore
|
||||
itemView.top < 0 -> getScrollRange()
|
||||
else -> 0
|
||||
}
|
||||
},
|
||||
)
|
||||
scrollToRestore = 0
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class WebtoonHolder(
|
||||
override fun onError(e: Throwable) {
|
||||
bindingInfo.textViewError.text = e.getDisplayMessage(context.resources)
|
||||
bindingInfo.buttonRetry.setText(
|
||||
ExceptionResolver.getResolveStringId(e).ifZero { R.string.try_again }
|
||||
ExceptionResolver.getResolveStringId(e).ifZero { R.string.try_again },
|
||||
)
|
||||
bindingInfo.layoutError.isVisible = true
|
||||
bindingInfo.progressBar.hideCompat()
|
||||
@@ -104,4 +104,4 @@ class WebtoonHolder(
|
||||
scrollToRestore = scroll
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.async
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.databinding.FragmentReaderWebtoonBinding
|
||||
import org.koitharu.kotatsu.reader.ui.ReaderState
|
||||
import org.koitharu.kotatsu.reader.ui.pager.BaseReader
|
||||
@@ -21,9 +19,6 @@ import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||
@AndroidEntryPoint
|
||||
class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private val scrollInterpolator = AccelerateDecelerateInterpolator()
|
||||
private var webtoonAdapter: WebtoonAdapter? = null
|
||||
|
||||
@@ -34,7 +29,7 @@ class WebtoonReaderFragment : BaseReader<FragmentReaderWebtoonBinding>() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
webtoonAdapter = WebtoonAdapter(viewModel.pageLoader, settings, exceptionResolver)
|
||||
webtoonAdapter = WebtoonAdapter(viewModel.pageLoader, viewModel.readerSettings, exceptionResolver)
|
||||
with(binding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
adapter = webtoonAdapter
|
||||
|
||||
Reference in New Issue
Block a user