Information bar in reader

This commit is contained in:
Koitharu
2022-08-02 15:02:26 +03:00
parent 6c43881cf4
commit c70ecd9cfd
11 changed files with 183 additions and 21 deletions

View File

@@ -11,12 +11,6 @@ import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.qualifiers.ApplicationContext
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.model.ZoomMode
import org.koitharu.kotatsu.core.network.DoHProvider
@@ -25,6 +19,12 @@ import org.koitharu.kotatsu.utils.ext.getEnumValue
import org.koitharu.kotatsu.utils.ext.observe
import org.koitharu.kotatsu.utils.ext.putEnumValue
import org.koitharu.kotatsu.utils.ext.toUriOrNull
import java.io.File
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class AppSettings @Inject constructor(@ApplicationContext context: Context) {
@@ -203,6 +203,9 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
val isSuggestionsExcludeNsfw: Boolean
get() = prefs.getBoolean(KEY_SUGGESTIONS_EXCLUDE_NSFW, false)
val isReaderBarEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_BAR, true)
val dnsOverHttps: DoHProvider
get() = prefs.getEnumValue(KEY_DOH, DoHProvider.NONE)
@@ -320,6 +323,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_EXIT_CONFIRM = "exit_confirm"
const val KEY_INCOGNITO_MODE = "incognito"
const val KEY_SYNC = "sync"
const val KEY_READER_BAR = "reader_bar"
// About
const val KEY_APP_UPDATE = "app_update"

View File

@@ -21,7 +21,7 @@ fun <T> AppSettings.observeAsFlow(key: String, valueProducer: AppSettings.() ->
fun <T> AppSettings.observeAsLiveData(
context: CoroutineContext,
key: String,
valueProducer: AppSettings.() -> T
valueProducer: AppSettings.() -> T,
) = liveData(context) {
emit(valueProducer())
observe().collect {
@@ -32,4 +32,4 @@ fun <T> AppSettings.observeAsLiveData(
}
}
}
}
}

View File

@@ -12,6 +12,7 @@ import androidx.annotation.AttrRes
import androidx.annotation.StyleRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.history.domain.PROGRESS_NONE
import org.koitharu.kotatsu.utils.ext.getAnimationDuration
class ReadingProgressView @JvmOverloads constructor(
context: Context,
@@ -20,7 +21,7 @@ class ReadingProgressView @JvmOverloads constructor(
) : View(context, attrs, defStyleAttr), ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener {
private var percentAnimator: ValueAnimator? = null
private val animationDuration = context.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
private val animationDuration = context.getAnimationDuration(android.R.integer.config_shortAnimTime)
@StyleRes
private val drawableStyle: Int
@@ -76,7 +77,7 @@ class ReadingProgressView @JvmOverloads constructor(
percentAnimator?.cancel()
percentAnimator = ValueAnimator.ofFloat(
currentDrawable.progress.coerceAtLeast(0f),
value
value,
).apply {
duration = animationDuration
interpolator = AccelerateDecelerateInterpolator()
@@ -111,4 +112,4 @@ class ReadingProgressView @JvmOverloads constructor(
outline.setOval(0, 0, view.width, view.height)
}
}
}
}

View File

@@ -5,16 +5,14 @@ import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.transition.Fade
import android.transition.Slide
import android.transition.TransitionManager
import android.transition.TransitionSet
import android.view.*
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.core.view.*
import androidx.lifecycle.lifecycleScope
import androidx.transition.Slide
import androidx.transition.TransitionManager
import androidx.transition.TransitionSet
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
@@ -101,6 +99,7 @@ class ReaderActivity :
onLoadingStateChanged(viewModel.isLoading.value == true)
}
viewModel.isScreenshotsBlockEnabled.observe(this, this::setWindowSecure)
viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)
viewModel.isBookmarkAdded.observe(this, this::onBookmarkStateChanged)
viewModel.onShowToast.observe(this) { msgId ->
Snackbar.make(binding.container, msgId, Snackbar.LENGTH_SHORT)
@@ -280,12 +279,14 @@ class ReaderActivity :
val transition = TransitionSet()
.setOrdering(TransitionSet.ORDERING_TOGETHER)
.addTransition(Slide(Gravity.TOP).addTarget(binding.appbarTop))
.addTransition(Fade().addTarget(binding.infoBar))
binding.appbarBottom?.let { bottomBar ->
transition.addTransition(Slide(Gravity.BOTTOM).addTarget(bottomBar))
}
TransitionManager.beginDelayedTransition(binding.root, transition)
binding.appbarTop.isVisible = isUiVisible
binding.appbarBottom?.isVisible = isUiVisible
binding.infoBar.isGone = isUiVisible || (viewModel.isInfoBarEnabled.value == false)
if (isUiVisible) {
showSystemUI()
} else {
@@ -322,6 +323,10 @@ class ReaderActivity :
setUiIsVisible(!binding.appbarTop.isVisible)
}
private fun onReaderBarChanged(isBarEnabled: Boolean) {
binding.infoBar.isVisible = isBarEnabled && binding.appbarTop.isGone
}
private fun onBookmarkStateChanged(isAdded: Boolean) {
val menuItem = binding.toolbarBottom.menu.findItem(R.id.action_bookmark) ?: return
menuItem.setTitle(if (isAdded) R.string.bookmark_remove else R.string.bookmark_add)
@@ -330,6 +335,7 @@ class ReaderActivity :
private fun onUiStateChanged(uiState: ReaderUiState?, previous: ReaderUiState?) {
title = uiState?.chapterName ?: uiState?.mangaName ?: getString(R.string.loading_)
binding.infoBar.update(uiState)
if (uiState == null) {
supportActionBar?.subtitle = null
binding.slider.isVisible = false

View File

@@ -0,0 +1,113 @@
package org.koitharu.kotatsu.reader.ui
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.icu.text.SimpleDateFormat
import android.util.AttributeSet
import android.view.View
import androidx.annotation.AttrRes
import androidx.core.graphics.ColorUtils
import com.google.android.material.R as materialR
import java.util.*
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
import org.koitharu.kotatsu.utils.ext.getThemeColor
import org.koitharu.kotatsu.utils.ext.resolveDp
class ReaderInfoBarView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val textBounds = Rect()
private val inset = context.resources.resolveDp(2f)
private val timeFormat = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT)
private val timeReceiver = TimeReceiver()
private var timeText = timeFormat.format(Date())
private var text: String = ""
private val innerHeight
get() = height - inset - inset - paddingTop - paddingBottom
private val innerWidth
get() = width - inset - inset - paddingLeft - paddingRight
init {
paint.color = ColorUtils.setAlphaComponent(
context.getThemeColor(materialR.attr.colorOnSurface, Color.BLACK),
160,
)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val ty = innerHeight / 2f + textBounds.height() / 2f - textBounds.bottom
paint.textAlign = Paint.Align.LEFT
canvas.drawText(text, paddingLeft + inset, paddingTop + inset + ty, paint)
paint.textAlign = Paint.Align.RIGHT
canvas.drawText(timeText, width - paddingRight - inset, paddingTop + inset + ty, paint)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
updateTextSize()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
context.registerReceiver(timeReceiver, IntentFilter(Intent.ACTION_TIME_TICK))
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
context.unregisterReceiver(timeReceiver)
}
fun update(state: ReaderUiState?) {
text = if (state != null) {
val percent = state.computePercent()
context.getString(
R.string.reader_info_pattern,
state.chapterNumber,
state.chaptersTotal,
state.currentPage + 1,
state.totalPages,
) + if (percent in 0f..1f) {
" " + context.getString(R.string.percent_string_pattern, (percent * 100).format())
} else {
""
}
} else {
""
}
updateTextSize()
invalidate()
}
private fun updateTextSize() {
val str = text + timeText
val testTextSize = 48f
paint.textSize = testTextSize
paint.getTextBounds(str, 0, str.length, textBounds)
paint.textSize = testTextSize * innerHeight / textBounds.height()
paint.getTextBounds(str, 0, str.length, textBounds)
}
private inner class TimeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
timeText = timeFormat.format(Date())
invalidate()
}
}
}

View File

@@ -80,6 +80,12 @@ class ReaderViewModel @AssistedInject constructor(
valueProducer = { readerAnimation },
)
val isInfoBarEnabled = settings.observeAsLiveData(
context = viewModelScope.coroutineContext + Dispatchers.Default,
key = AppSettings.KEY_READER_BAR,
valueProducer = { isReaderBarEnabled },
)
val isScreenshotsBlockEnabled = combine(
mangaData,
settings.observeAsFlow(AppSettings.KEY_SCREENSHOTS_POLICY) { screenshotsPolicy },

View File

@@ -7,4 +7,12 @@ data class ReaderUiState(
val chaptersTotal: Int,
val currentPage: Int,
val totalPages: Int,
)
) {
fun computePercent(): Float {
val ppc = 1f / chaptersTotal
val chapterIndex = chapterNumber - 1
val pagePercent = (currentPage + 1) / totalPages.toFloat()
return ppc * chapterIndex + ppc * pagePercent
}
}

View File

@@ -12,6 +12,15 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
<org.koitharu.kotatsu.reader.ui.ReaderInfoBarView
android:id="@+id/infoBar"
android:layout_width="match_parent"
android:layout_height="16dp"
android:layout_gravity="top"
android:paddingHorizontal="6dp"
android:visibility="gone"
tools:visibility="visible" />
<org.koitharu.kotatsu.reader.ui.ReaderToastView
android:id="@+id/toastView"
android:layout_width="wrap_content"

View File

@@ -25,6 +25,15 @@
android:theme="@style/ThemeOverlay.Material3.Dark"
tools:text="@string/loading_" />
<org.koitharu.kotatsu.reader.ui.ReaderInfoBarView
android:id="@+id/infoBar"
android:layout_width="match_parent"
android:layout_height="16dp"
android:layout_gravity="top"
android:paddingHorizontal="4dp"
android:visibility="gone"
tools:visibility="visible" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar_top"
android:layout_width="match_parent"

View File

@@ -364,4 +364,6 @@
<string name="automatic_scroll">Automatic scroll</string>
<string name="off_short">Off</string>
<string name="seconds_pattern">%ss</string>
<string name="reader_info_pattern">Ch. %1$d/%2$d Pg. %3$d/%4$d</string>
<string name="reader_info_bar">Show information bar in reader</string>
</resources>

View File

@@ -34,6 +34,11 @@
android:key="reader_animation"
android:title="@string/pages_animation" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="reader_bar"
android:title="@string/reader_info_bar" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pages_numbers"
@@ -45,7 +50,6 @@
android:key="screenshots_policy"
android:title="@string/screenshots_policy"
app:defaultValue="allow"
app:useSimpleSummaryProvider="true" />
<ListPreference
@@ -57,4 +61,4 @@
app:useSimpleSummaryProvider="true" />
</PreferenceScreen>
</PreferenceScreen>