From c70ecd9cfdd30f23c8133ae75a1cbc4975949966 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Tue, 2 Aug 2022 15:02:26 +0300 Subject: [PATCH] Information bar in reader --- .../kotatsu/core/prefs/AppSettings.kt | 16 ++- .../kotatsu/core/prefs/AppSettingsObserver.kt | 4 +- .../history/ui/util/ReadingProgressView.kt | 7 +- .../kotatsu/reader/ui/ReaderActivity.kt | 20 ++-- .../kotatsu/reader/ui/ReaderInfoBarView.kt | 113 ++++++++++++++++++ .../kotatsu/reader/ui/ReaderViewModel.kt | 6 + .../kotatsu/reader/ui/pager/ReaderUiState.kt | 10 +- .../res/layout-w600dp/activity_reader.xml | 9 ++ app/src/main/res/layout/activity_reader.xml | 9 ++ app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/pref_reader.xml | 8 +- 11 files changed, 183 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt 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 6838c160f..53360ca0a 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 @@ -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" diff --git a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt index 88c62514c..a8e46ccc2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/prefs/AppSettingsObserver.kt @@ -21,7 +21,7 @@ fun AppSettings.observeAsFlow(key: String, valueProducer: AppSettings.() -> fun AppSettings.observeAsLiveData( context: CoroutineContext, key: String, - valueProducer: AppSettings.() -> T + valueProducer: AppSettings.() -> T, ) = liveData(context) { emit(valueProducer()) observe().collect { @@ -32,4 +32,4 @@ fun AppSettings.observeAsLiveData( } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/history/ui/util/ReadingProgressView.kt b/app/src/main/java/org/koitharu/kotatsu/history/ui/util/ReadingProgressView.kt index 2bb906c99..fdf161282 100644 --- a/app/src/main/java/org/koitharu/kotatsu/history/ui/util/ReadingProgressView.kt +++ b/app/src/main/java/org/koitharu/kotatsu/history/ui/util/ReadingProgressView.kt @@ -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) } } -} \ No newline at end of file +} 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 867938511..7b8a10813 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 @@ -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 diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt new file mode 100644 index 000000000..5f825fee6 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/ReaderInfoBarView.kt @@ -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() + } + } +} 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 ebb57e2a1..dc558dc3b 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 @@ -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 }, diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt index f306d8837..7014b1a5e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/pager/ReaderUiState.kt @@ -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 + } +} diff --git a/app/src/main/res/layout-w600dp/activity_reader.xml b/app/src/main/res/layout-w600dp/activity_reader.xml index 48807b155..0e185ece1 100644 --- a/app/src/main/res/layout-w600dp/activity_reader.xml +++ b/app/src/main/res/layout-w600dp/activity_reader.xml @@ -12,6 +12,15 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + + + + Automatic scroll Off %ss + Ch. %1$d/%2$d Pg. %3$d/%4$d + Show information bar in reader diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml index 42e46b8ad..9d82c1cf3 100644 --- a/app/src/main/res/xml/pref_reader.xml +++ b/app/src/main/res/xml/pref_reader.xml @@ -34,6 +34,11 @@ android:key="reader_animation" android:title="@string/pages_animation" /> + + - \ No newline at end of file +