diff --git a/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt index 94b4cb37e..dbdb11b1e 100644 --- a/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/readbook/ReadBookActivity.kt @@ -7,6 +7,7 @@ import android.os.Bundle import android.view.KeyEvent import android.view.Menu import android.view.MenuItem +import android.view.WindowManager import androidx.lifecycle.Observer import io.legado.app.R import io.legado.app.base.VMBaseActivity @@ -92,6 +93,18 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_rea } } read_menu.setListener(object : ReadMenu.Callback { + override fun setScreenBrightness(value: Int) { + var brightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE + if (this@ReadBookActivity.getPrefBoolean("brightnessAuto").not()) { + brightness = value.toFloat() + if (brightness < 1f) brightness = 1f + brightness = brightness * 1.0f / 255f + } + val params = window.attributes + params.screenBrightness = brightness + window.attributes = params + } + override fun autoPage() { } @@ -286,7 +299,8 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_rea super.onActivityResult(requestCode, resultCode, data) if (resultCode == Activity.RESULT_OK) { when (requestCode) { - + requestCodeEditSource -> { + } } } } diff --git a/app/src/main/java/io/legado/app/ui/readbook/ReadMenu.kt b/app/src/main/java/io/legado/app/ui/readbook/ReadMenu.kt index 6fde199ab..94db4efc8 100644 --- a/app/src/main/java/io/legado/app/ui/readbook/ReadMenu.kt +++ b/app/src/main/java/io/legado/app/ui/readbook/ReadMenu.kt @@ -10,6 +10,8 @@ import androidx.core.view.isVisible import io.legado.app.App import io.legado.app.R import io.legado.app.constant.Bus +import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.buttonDisabledColor import io.legado.app.utils.* import kotlinx.android.synthetic.main.view_read_menu.view.* import org.jetbrains.anko.sdk27.listeners.onClick @@ -44,6 +46,19 @@ class ReadMenu : FrameLayout { initAnimation() vw_bg.onClick { } vwNavigationBar.onClick { } + seek_brightness.progress = context.getPrefInt("brightness", 100) + upBrightness() + } + + private fun upBrightness() { + if (context.getPrefBoolean("brightnessAuto", true)) { + iv_brightness_auto.setColorFilter(context.accentColor) + seek_brightness.isEnabled = false + } else { + iv_brightness_auto.setColorFilter(context.buttonDisabledColor) + seek_brightness.isEnabled = true + } + callback?.setScreenBrightness(context.getPrefInt("brightness", 100)) } fun setListener(callback: Callback) { @@ -51,45 +66,6 @@ class ReadMenu : FrameLayout { bindEvent() } - private fun initAnimation() { - menuTopIn = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_top_in) - menuBottomIn = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_bottom_in) - menuBottomIn.setAnimationListener(object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation) { - - } - - override fun onAnimationEnd(animation: Animation) { - vw_menu_bg.onClick { runMenuOut() } - } - - override fun onAnimationRepeat(animation: Animation) { - - } - }) - - //隐藏菜单 - menuTopOut = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_top_out) - menuBottomOut = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_bottom_out) - menuBottomOut.setAnimationListener(object : Animation.AnimationListener { - override fun onAnimationStart(animation: Animation) { - vw_menu_bg.setOnClickListener(null) - } - - override fun onAnimationEnd(animation: Animation) { - this@ReadMenu.invisible() - title_bar.invisible() - bottom_menu.invisible() - menuBarShow = false - onMenuOutEnd?.invoke() - } - - override fun onAnimationRepeat(animation: Animation) { - - } - }) - } - fun runMenuIn() { this.visible() title_bar.visible() @@ -114,7 +90,27 @@ class ReadMenu : FrameLayout { } private fun bindEvent() { - ll_floating_button.onClick { runMenuOut() } + iv_brightness_auto.onClick { + context.putPrefBoolean( + "brightnessAuto", + !context.getPrefBoolean("brightnessAuto", true) + ) + upBrightness() + } + seek_brightness.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + callback?.setScreenBrightness(progress) + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) { + + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + context.putPrefInt("brightness", seek_brightness.progress) + } + + }) //阅读进度 seek_read_page.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -177,6 +173,45 @@ class ReadMenu : FrameLayout { } } + private fun initAnimation() { + menuTopIn = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_top_in) + menuBottomIn = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_bottom_in) + menuBottomIn.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + + } + + override fun onAnimationEnd(animation: Animation) { + vw_menu_bg.onClick { runMenuOut() } + } + + override fun onAnimationRepeat(animation: Animation) { + + } + }) + + //隐藏菜单 + menuTopOut = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_top_out) + menuBottomOut = AnimationUtils.loadAnimation(context, R.anim.anim_readbook_bottom_out) + menuBottomOut.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation) { + vw_menu_bg.setOnClickListener(null) + } + + override fun onAnimationEnd(animation: Animation) { + this@ReadMenu.invisible() + title_bar.invisible() + bottom_menu.invisible() + menuBarShow = false + onMenuOutEnd?.invoke() + } + + override fun onAnimationRepeat(animation: Animation) { + + } + }) + } + fun setAutoPage(autoPage: Boolean) { if (autoPage) { fabAutoPage.setImageResource(R.drawable.ic_auto_page_stop) @@ -188,21 +223,14 @@ class ReadMenu : FrameLayout { } interface Callback { - + fun setScreenBrightness(value: Int) fun autoPage() - fun skipToPage(page: Int) - fun skipPreChapter() - fun skipNextChapter() - fun openReplaceRule() - fun openChapterList() - fun showReadStyle() - fun showMoreSetting() } diff --git a/app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBar.kt b/app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBar.kt new file mode 100644 index 000000000..f08e2b3c6 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBar.kt @@ -0,0 +1,363 @@ +package io.legado.app.ui.widget.seekbar + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.KeyEvent +import android.view.MotionEvent +import android.widget.ProgressBar +import androidx.appcompat.widget.AppCompatSeekBar +import androidx.core.view.ViewCompat +import io.legado.app.R +import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.ThemeStore +import java.lang.reflect.InvocationTargetException +import java.lang.reflect.Method + +class VerticalSeekBar : AppCompatSeekBar { + + private var mIsDragging: Boolean = false + private var mThumb: Drawable? = null + private var mMethodSetProgressFromUser: Method? = null + private var mRotationAngle = ROTATION_ANGLE_CW_90 + + var rotationAngle: Int + get() = mRotationAngle + set(angle) { + require(isValidRotationAngle(angle)) { "Invalid angle specified :$angle" } + + if (mRotationAngle == angle) { + return + } + + mRotationAngle = angle + + if (useViewRotation()) { + val wrapper = wrapper + wrapper?.applyViewRotation() + } else { + requestLayout() + } + } + + private val wrapper: VerticalSeekBarWrapper? + get() { + val parent = parent + + return if (parent is VerticalSeekBarWrapper) { + parent + } else { + null + } + } + + constructor(context: Context) : super(context) { + initialize(context, null, 0, 0) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + initialize(context, attrs, 0, 0) + } + + constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super( + context, + attrs, + defStyle + ) { + initialize(context, attrs, defStyle, 0) + } + + private fun initialize( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) { + ATH.setTint(this, ThemeStore.accentColor(context)) + ViewCompat.setLayoutDirection(this, ViewCompat.LAYOUT_DIRECTION_LTR) + + if (attrs != null) { + val a = context.obtainStyledAttributes( + attrs, + R.styleable.VerticalSeekBar, + defStyleAttr, + defStyleRes + ) + val rotationAngle = a.getInteger(R.styleable.VerticalSeekBar_seekBarRotation, 0) + if (isValidRotationAngle(rotationAngle)) { + mRotationAngle = rotationAngle + } + a.recycle() + } + } + + override fun setThumb(thumb: Drawable) { + mThumb = thumb + super.setThumb(thumb) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + return if (useViewRotation()) { + onTouchEventUseViewRotation(event) + } else { + onTouchEventTraditionalRotation(event) + } + } + + private fun onTouchEventTraditionalRotation(event: MotionEvent): Boolean { + if (!isEnabled) { + return false + } + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + isPressed = true + onStartTrackingTouch() + trackTouchEvent(event) + attemptClaimDrag(true) + invalidate() + } + + MotionEvent.ACTION_MOVE -> if (mIsDragging) { + trackTouchEvent(event) + } + + MotionEvent.ACTION_UP -> { + if (mIsDragging) { + trackTouchEvent(event) + onStopTrackingTouch() + isPressed = false + } else { + // Touch up when we never crossed the touch slop threshold + // should + // be interpreted as a tap-seek to that location. + onStartTrackingTouch() + trackTouchEvent(event) + onStopTrackingTouch() + attemptClaimDrag(false) + } + // ProgressBar doesn't know to repaint the thumb drawable + // in its inactive state when the touch stops (because the + // value has not apparently changed) + invalidate() + } + + MotionEvent.ACTION_CANCEL -> { + if (mIsDragging) { + onStopTrackingTouch() + isPressed = false + } + invalidate() // see above explanation + } + } + return true + } + + private fun onTouchEventUseViewRotation(event: MotionEvent): Boolean { + val handled = super.onTouchEvent(event) + + if (handled) { + when (event.action) { + MotionEvent.ACTION_DOWN -> attemptClaimDrag(true) + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> attemptClaimDrag(false) + } + } + + return handled + } + + private fun trackTouchEvent(event: MotionEvent) { + val paddingLeft = super.getPaddingLeft() + val paddingRight = super.getPaddingRight() + val height = height + + val available = height - paddingLeft - paddingRight + val y = event.y.toInt() + + val scale: Float + var value = 0f + + when (mRotationAngle) { + ROTATION_ANGLE_CW_90 -> value = (y - paddingLeft).toFloat() + ROTATION_ANGLE_CW_270 -> value = (height - paddingLeft - y).toFloat() + } + + scale = if (value < 0 || available == 0) { + 0.0f + } else if (value > available) { + 1.0f + } else { + value / available.toFloat() + } + + val max = max + val progress = scale * max + + setProgressFromUser(progress.toInt(), true) + } + + /** + * Tries to claim the user's drag motion, and requests disallowing any + * ancestors from stealing events in the drag. + */ + private fun attemptClaimDrag(active: Boolean) { + val parent = parent + parent?.requestDisallowInterceptTouchEvent(active) + } + + /** + * This is called when the user has started touching this widget. + */ + private fun onStartTrackingTouch() { + mIsDragging = true + } + + /** + * This is called when the user either releases his touch or the touch is + * canceled. + */ + private fun onStopTrackingTouch() { + mIsDragging = false + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + if (isEnabled) { + val handled: Boolean + var direction = 0 + + when (keyCode) { + KeyEvent.KEYCODE_DPAD_DOWN -> { + direction = if (mRotationAngle == ROTATION_ANGLE_CW_90) 1 else -1 + handled = true + } + KeyEvent.KEYCODE_DPAD_UP -> { + direction = if (mRotationAngle == ROTATION_ANGLE_CW_270) 1 else -1 + handled = true + } + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT -> + // move view focus to previous/next view + return false + else -> handled = false + } + + if (handled) { + val keyProgressIncrement = keyProgressIncrement + var progress = progress + + progress += direction * keyProgressIncrement + + if (progress in 0..max) { + setProgressFromUser(progress, true) + } + + return true + } + } + + return super.onKeyDown(keyCode, event) + } + + @Synchronized + override fun setProgress(progress: Int) { + super.setProgress(progress) + if (!useViewRotation()) { + refreshThumb() + } + } + + @Synchronized + private fun setProgressFromUser(progress: Int, fromUser: Boolean) { + if (mMethodSetProgressFromUser == null) { + try { + val m: Method = ProgressBar::class.java.getDeclaredMethod( + "setProgress", + Int::class.javaPrimitiveType, + Boolean::class.javaPrimitiveType + ) + m.isAccessible = true + mMethodSetProgressFromUser = m + } catch (e: NoSuchMethodException) { + } + + } + + if (mMethodSetProgressFromUser != null) { + try { + mMethodSetProgressFromUser!!.invoke(this, progress, fromUser) + } catch (e: IllegalArgumentException) { + } catch (e: IllegalAccessException) { + } catch (e: InvocationTargetException) { + } + + } else { + super.setProgress(progress) + } + refreshThumb() + } + + @Synchronized + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if (useViewRotation()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } else { + super.onMeasure(heightMeasureSpec, widthMeasureSpec) + + val lp = layoutParams + + if (isInEditMode && lp != null && lp.height >= 0) { + setMeasuredDimension(super.getMeasuredHeight(), lp.height) + } else { + setMeasuredDimension(super.getMeasuredHeight(), super.getMeasuredWidth()) + } + } + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + if (useViewRotation()) { + super.onSizeChanged(w, h, oldw, oldh) + } else { + super.onSizeChanged(h, w, oldh, oldw) + } + } + + @Synchronized + override fun onDraw(canvas: Canvas) { + if (!useViewRotation()) { + when (mRotationAngle) { + ROTATION_ANGLE_CW_90 -> { + canvas.rotate(90f) + canvas.translate(0f, (-super.getWidth()).toFloat()) + } + ROTATION_ANGLE_CW_270 -> { + canvas.rotate(-90f) + canvas.translate((-super.getHeight()).toFloat(), 0f) + } + } + } + + super.onDraw(canvas) + } + + // refresh thumb position + private fun refreshThumb() { + onSizeChanged(super.getWidth(), super.getHeight(), 0, 0) + } + + /*package*/ + internal fun useViewRotation(): Boolean { + return !isInEditMode + } + + companion object { + const val ROTATION_ANGLE_CW_90 = 90 + const val ROTATION_ANGLE_CW_270 = 270 + + private fun isValidRotationAngle(angle: Int): Boolean { + return angle == ROTATION_ANGLE_CW_90 || angle == ROTATION_ANGLE_CW_270 + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBarWrapper.kt b/app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBarWrapper.kt new file mode 100644 index 000000000..da69c33ab --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBarWrapper.kt @@ -0,0 +1,184 @@ +package io.legado.app.ui.widget.seekbar + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout + +import androidx.core.view.ViewCompat +import kotlin.math.max + +class VerticalSeekBarWrapper @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + + private val childSeekBar: VerticalSeekBar? + get() { + val child = if (childCount > 0) getChildAt(0) else null + return if (child is VerticalSeekBar) child else null + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + if (useViewRotation()) { + onSizeChangedUseViewRotation(w, h, oldw, oldh) + } else { + onSizeChangedTraditionalRotation(w, h, oldw, oldh) + } + } + + @SuppressLint("RtlHardcoded") + private fun onSizeChangedTraditionalRotation(w: Int, h: Int, oldw: Int, oldh: Int) { + val seekBar = childSeekBar + + if (seekBar != null) { + val hPadding = paddingLeft + paddingRight + val vPadding = paddingTop + paddingBottom + val lp = seekBar.layoutParams as LayoutParams + + lp.width = ViewGroup.LayoutParams.WRAP_CONTENT + lp.height = max(0, h - vPadding) + seekBar.layoutParams = lp + + seekBar.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED) + + val seekBarMeasuredWidth = seekBar.measuredWidth + seekBar.measure( + MeasureSpec.makeMeasureSpec( + max(0, w - hPadding), + MeasureSpec.AT_MOST + ), + MeasureSpec.makeMeasureSpec( + max(0, h - vPadding), + MeasureSpec.EXACTLY + ) + ) + + lp.gravity = Gravity.TOP or Gravity.LEFT + lp.leftMargin = (max(0, w - hPadding) - seekBarMeasuredWidth) / 2 + seekBar.layoutParams = lp + } + + super.onSizeChanged(w, h, oldw, oldh) + } + + private fun onSizeChangedUseViewRotation(w: Int, h: Int, oldw: Int, oldh: Int) { + val seekBar = childSeekBar + + if (seekBar != null) { + val hPadding = paddingLeft + paddingRight + val vPadding = paddingTop + paddingBottom + seekBar.measure( + MeasureSpec.makeMeasureSpec( + max(0, h - vPadding), + MeasureSpec.EXACTLY + ), + MeasureSpec.makeMeasureSpec( + max(0, w - hPadding), + MeasureSpec.AT_MOST + ) + ) + } + + applyViewRotation(w, h) + super.onSizeChanged(w, h, oldw, oldh) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val seekBar = childSeekBar + val widthMode = MeasureSpec.getMode(widthMeasureSpec) + val heightMode = MeasureSpec.getMode(heightMeasureSpec) + val widthSize = MeasureSpec.getSize(widthMeasureSpec) + val heightSize = MeasureSpec.getSize(heightMeasureSpec) + + if (seekBar != null && widthMode != MeasureSpec.EXACTLY) { + val seekBarWidth: Int + val seekBarHeight: Int + val hPadding = paddingLeft + paddingRight + val vPadding = paddingTop + paddingBottom + val innerContentWidthMeasureSpec = + MeasureSpec.makeMeasureSpec(max(0, widthSize - hPadding), widthMode) + val innerContentHeightMeasureSpec = + MeasureSpec.makeMeasureSpec(max(0, heightSize - vPadding), heightMode) + + if (useViewRotation()) { + seekBar.measure(innerContentHeightMeasureSpec, innerContentWidthMeasureSpec) + seekBarWidth = seekBar.measuredHeight + seekBarHeight = seekBar.measuredWidth + } else { + seekBar.measure(innerContentWidthMeasureSpec, innerContentHeightMeasureSpec) + seekBarWidth = seekBar.measuredWidth + seekBarHeight = seekBar.measuredHeight + } + + val measuredWidth = + View.resolveSizeAndState(seekBarWidth + hPadding, widthMeasureSpec, 0) + val measuredHeight = + View.resolveSizeAndState(seekBarHeight + vPadding, heightMeasureSpec, 0) + + setMeasuredDimension(measuredWidth, measuredHeight) + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + } + + /*package*/ + internal fun applyViewRotation() { + applyViewRotation(width, height) + } + + private fun applyViewRotation(w: Int, h: Int) { + val seekBar = childSeekBar + + if (seekBar != null) { + val isLTR = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR + val rotationAngle = seekBar.rotationAngle + val seekBarMeasuredWidth = seekBar.measuredWidth + val seekBarMeasuredHeight = seekBar.measuredHeight + val hPadding = paddingLeft + paddingRight + val vPadding = paddingTop + paddingBottom + val hOffset = (max(0, w - hPadding) - seekBarMeasuredHeight) * 0.5f + val lp = seekBar.layoutParams + + lp.width = max(0, h - vPadding) + lp.height = ViewGroup.LayoutParams.WRAP_CONTENT + + seekBar.layoutParams = lp + + seekBar.pivotX = (if (isLTR) 0 else max(0, h - vPadding)).toFloat() + seekBar.pivotY = 0f + + when (rotationAngle) { + VerticalSeekBar.ROTATION_ANGLE_CW_90 -> { + seekBar.rotation = 90f + if (isLTR) { + seekBar.translationX = seekBarMeasuredHeight + hOffset + seekBar.translationY = 0f + } else { + seekBar.translationX = -hOffset + seekBar.translationY = seekBarMeasuredWidth.toFloat() + } + } + VerticalSeekBar.ROTATION_ANGLE_CW_270 -> { + seekBar.rotation = 270f + if (isLTR) { + seekBar.translationX = hOffset + seekBar.translationY = seekBarMeasuredWidth.toFloat() + } else { + seekBar.translationX = -(seekBarMeasuredHeight + hOffset) + seekBar.translationY = 0f + } + } + } + } + } + + private fun useViewRotation(): Boolean { + val seekBar = childSeekBar + return seekBar?.useViewRotation() ?: false + } +} diff --git a/app/src/main/res/drawable/ic_brightness_auto.xml b/app/src/main/res/drawable/ic_brightness_auto.xml new file mode 100644 index 000000000..6ea7b953e --- /dev/null +++ b/app/src/main/res/drawable/ic_brightness_auto.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_read_menu.xml b/app/src/main/res/layout/view_read_menu.xml index ba84bece8..b1f3ea57b 100644 --- a/app/src/main/res/layout/view_read_menu.xml +++ b/app/src/main/res/layout/view_read_menu.xml @@ -42,6 +42,33 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43793d206..0c7c90e4a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -483,5 +483,6 @@ 阅读 %d%% %d分钟 + 自动亮度%s