Information bar in reader

pull/189/head
Koitharu 4 years ago
parent 6c43881cf4
commit c70ecd9cfd
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

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

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

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

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

@ -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()
}
}
}

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

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

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

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

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

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

Loading…
Cancel
Save