浏览代码

Custom color filter for reader (#434)

* [WIP] Custom color filter for reader

* Improvements

* temp image to prevent build error

* Shift all the bits

* Some improvements. Removed DiscreteSeekBar

* Improvements

* API 16 + fixes

* Reduced lag. Fixed brightness value being reset to 0

* Small fixes
Bram van de Kerkhof 8 年之前
父节点
当前提交
8be67a4431

+ 0 - 1
app/build.gradle

@@ -165,7 +165,6 @@ dependencies {
     compile 'com.afollestad.material-dialogs:core:0.8.6.2'
     compile 'net.xpece.android:support-preference:0.8.1'
     compile 'me.zhanghai.android.systemuihelper:library:1.0.0'
-    compile 'org.adw.library:discrete-seekbar:1.0.1'
     compile 'de.hdodenhof:circleimageview:2.1.0'
 
     // Tests

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

@@ -26,6 +26,10 @@ class PreferenceKeys(context: Context) {
 
     val customBrightnessValue = context.getString(R.string.pref_custom_brightness_value_key)
 
+    val colorFilter = context.getString(R.string.pref_color_filter_key)
+
+    val colorFilterValue = context.getString(R.string.pref_color_filter_value_key)
+
     val defaultViewer = context.getString(R.string.pref_default_viewer_key)
 
     val imageScaleType = context.getString(R.string.pref_image_scale_type_key)

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

@@ -52,6 +52,10 @@ class PreferencesHelper(context: Context) {
 
     fun customBrightnessValue() = rxPrefs.getInteger(keys.customBrightnessValue, 0)
 
+    fun colorFilter() = rxPrefs.getBoolean(keys.colorFilter, false)
+
+    fun colorFilterValue() = rxPrefs.getInteger(keys.colorFilterValue, 0)
+
     fun defaultViewer() = prefs.getInt(keys.defaultViewer, 1)
 
     fun imageScaleType() = rxPrefs.getInteger(keys.imageScaleType, 1)

+ 29 - 2
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -38,10 +38,12 @@ import me.zhanghai.android.systemuihelper.SystemUiHelper
 import me.zhanghai.android.systemuihelper.SystemUiHelper.*
 import nucleus.factory.RequiresPresenter
 import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
 import rx.subscriptions.CompositeSubscription
 import timber.log.Timber
 import uy.kohesive.injekt.injectLazy
 import java.text.DecimalFormat
+import java.util.concurrent.TimeUnit
 
 @RequiresPresenter(ReaderPresenter::class)
 class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
@@ -70,6 +72,8 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
 
     private var customBrightnessSubscription: Subscription? = null
 
+    private var customFilterColorSubscription: Subscription? = null
+
     var readerTheme: Int = 0
         private set
 
@@ -140,6 +144,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
         when (item.itemId) {
             R.id.action_settings -> ReaderSettingsDialog().show(supportFragmentManager, "settings")
+            R.id.action_custom_filter -> ReaderCustomFilterDialog().show(supportFragmentManager, "filter")
             else -> return super.onOptionsItemSelected(item)
         }
         return true
@@ -354,9 +359,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         reader_menu_bottom.setOnTouchListener { v, event -> true }
 
         page_seekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
-            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
+            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
                 if (fromUser) {
-                    gotoPageInCurrentChapter(progress)
+                    gotoPageInCurrentChapter(value)
                 }
             }
         })
@@ -378,6 +383,9 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         subscriptions += preferences.customBrightness().asObservable()
                 .subscribe { setCustomBrightness(it) }
 
+        subscriptions += preferences.colorFilter().asObservable()
+                .subscribe { setColorFilter(it) }
+
         subscriptions += preferences.readerTheme().asObservable()
                 .distinctUntilChanged()
                 .subscribe { applyTheme(it) }
@@ -424,6 +432,7 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
     private fun setCustomBrightness(enabled: Boolean) {
         if (enabled) {
             customBrightnessSubscription = preferences.customBrightnessValue().asObservable()
+                    .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
                     .subscribe { setCustomBrightnessValue(it) }
 
             subscriptions.add(customBrightnessSubscription)
@@ -433,6 +442,19 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         }
     }
 
+    private fun setColorFilter(enabled: Boolean) {
+        if (enabled) {
+            customFilterColorSubscription = preferences.colorFilterValue().asObservable()
+                    .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
+                    .subscribe { setColorFilterValue(it) }
+
+            subscriptions.add(customFilterColorSubscription)
+        } else {
+            customFilterColorSubscription?.let { subscriptions.remove(it) }
+            color_overlay.visibility = View.GONE
+        }
+    }
+
     /**
      * Sets the brightness of the screen. Range is [-75, 100].
      * From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
@@ -459,6 +481,11 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
         }
     }
 
+    private fun setColorFilterValue(value: Int) {
+        color_overlay.visibility = View.VISIBLE
+        color_overlay.setBackgroundColor(value)
+    }
+
     private fun applyTheme(theme: Int) {
         readerTheme = theme
         val rootView = window.decorView.rootView

+ 329 - 0
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderCustomFilterDialog.kt

@@ -0,0 +1,329 @@
+package eu.kanade.tachiyomi.ui.reader
+
+import android.app.Dialog
+import android.graphics.Color
+import android.os.Bundle
+import android.support.annotation.ColorInt
+import android.support.v4.app.DialogFragment
+import android.view.View
+import android.widget.SeekBar
+import com.afollestad.materialdialogs.MaterialDialog
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.preference.getOrDefault
+import eu.kanade.tachiyomi.util.plusAssign
+import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
+import kotlinx.android.synthetic.main.dialog_reader_custom_filter.view.*
+import rx.Subscription
+import rx.android.schedulers.AndroidSchedulers
+import rx.subscriptions.CompositeSubscription
+import uy.kohesive.injekt.injectLazy
+import java.util.concurrent.TimeUnit
+
+/**
+ * Custom dialog which can be used to set overlay value's
+ */
+class ReaderCustomFilterDialog : DialogFragment() {
+
+    companion object {
+        /** Integer mask of alpha value **/
+        private const val ALPHA_MASK: Long = 0xFF000000
+
+        /** Integer mask of red value **/
+        private const val RED_MASK: Long = 0x00FF0000
+
+        /** Integer mask of green value **/
+        private const val GREEN_MASK: Long = 0x0000FF00
+
+        /** Integer mask of blue value **/
+        private const val BLUE_MASK: Long = 0x000000FF
+    }
+
+    /**
+     * Provides operations to manage preferences
+     */
+    private val preferences by injectLazy<PreferencesHelper>()
+
+    /**
+     * Subscription used for filter overlay
+     */
+    private lateinit var subscriptions: CompositeSubscription
+
+    /**
+     * Subscription used for custom brightness overlay
+     */
+    private var customBrightnessSubscription: Subscription? = null
+
+    /**
+     * Subscription used for color filter overlay
+     */
+    private var customFilterColorSubscription: Subscription? = null
+
+    /**
+     * This method will be called after onCreate(Bundle)
+     * @param savedState The last saved instance state of the Fragment.
+     */
+    override fun onCreateDialog(savedState: Bundle?): Dialog {
+        val dialog = MaterialDialog.Builder(activity)
+                .customView(R.layout.dialog_reader_custom_filter, false)
+                .positiveText(android.R.string.ok)
+                .build()
+
+        subscriptions = CompositeSubscription()
+        onViewCreated(dialog.view, savedState)
+
+        return dialog
+    }
+
+    /**
+     * Called immediately after onCreateView()
+     * @param view The View returned by onCreateDialog.
+     * @param savedInstanceState If non-null, this fragment is being re-constructed
+     */
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(view) {
+        // Initialize subscriptions.
+        subscriptions += preferences.colorFilter().asObservable()
+                .subscribe { setColorFilter(it, view) }
+
+        subscriptions += preferences.customBrightness().asObservable()
+                .subscribe { setCustomBrightness(it, view) }
+
+        // Get color and update values
+        val color = preferences.colorFilterValue().getOrDefault()
+        val brightness = preferences.customBrightnessValue().getOrDefault()
+
+        val argb = setValues(color, view)
+
+        // Set brightness value
+        txt_brightness_seekbar_value.text = brightness.toString()
+
+        // Initialize seekBar progress
+        seekbar_color_filter_alpha.progress = argb[0]
+        seekbar_color_filter_red.progress = argb[1]
+        seekbar_color_filter_green.progress = argb[2]
+        seekbar_color_filter_blue.progress = argb[3]
+
+        // Set listeners
+        switch_color_filter.isChecked = preferences.colorFilter().getOrDefault()
+        switch_color_filter.setOnCheckedChangeListener { v, isChecked ->
+            preferences.colorFilter().set(isChecked)
+        }
+
+        custom_brightness.isChecked = preferences.customBrightness().getOrDefault()
+        custom_brightness.setOnCheckedChangeListener { v, isChecked ->
+            preferences.customBrightness().set(isChecked)
+        }
+
+        seekbar_color_filter_alpha.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
+            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                if (fromUser) {
+                    setColorValue(value, ALPHA_MASK, 24)
+                }
+            }
+        })
+
+        seekbar_color_filter_red.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
+            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                if (fromUser) {
+                    setColorValue(value, RED_MASK, 16)
+                }
+            }
+        })
+
+        seekbar_color_filter_green.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
+            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                if (fromUser) {
+                    setColorValue(value, GREEN_MASK, 8)
+                }
+            }
+        })
+
+        seekbar_color_filter_blue.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
+            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                if (fromUser) {
+                    setColorValue(value, BLUE_MASK, 0)
+                }
+            }
+        })
+        brightness_seekbar.progress = preferences.customBrightnessValue().getOrDefault()
+        brightness_seekbar.setOnSeekBarChangeListener(object : SimpleSeekBarListener() {
+            override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+                if (fromUser) {
+                    preferences.customBrightnessValue().set(value)
+                }
+            }
+        })
+
+    }
+
+    /**
+     * Set enabled status of seekBars belonging to color filter
+     * @param enabled determines if seekBar gets enabled
+     * @param view view of the dialog
+     */
+    private fun setColorFilterSeekBar(enabled: Boolean, view: View) = with(view) {
+        seekbar_color_filter_red.isEnabled = enabled
+        seekbar_color_filter_green.isEnabled = enabled
+        seekbar_color_filter_blue.isEnabled = enabled
+        seekbar_color_filter_alpha.isEnabled = enabled
+    }
+
+    /**
+     * Set enabled status of seekBars belonging to custom brightness
+     * @param enabled value which determines if seekBar gets enabled
+     * @param view view of the dialog
+     */
+    private fun setCustomBrightnessSeekBar(enabled: Boolean, view: View) = with(view) {
+        brightness_seekbar.isEnabled = enabled
+    }
+
+    /**
+     * Set the text value's of color filter
+     * @param color integer containing color information
+     * @param view view of the dialog
+     */
+    fun setValues(color: Int, view: View): Array<Int> {
+        val alpha = getAlphaFromColor(color)
+        val red = getRedFromColor(color)
+        val green = getGreenFromColor(color)
+        val blue = getBlueFromColor(color)
+
+        //Initialize values
+        with(view) {
+            txt_color_filter_alpha_value.text = alpha.toString()
+
+            txt_color_filter_red_value.text = red.toString()
+
+            txt_color_filter_green_value.text = green.toString()
+
+            txt_color_filter_blue_value.text = blue.toString()
+        }
+        return arrayOf(alpha, red, green, blue)
+    }
+
+    /**
+     * Manages the custom brightness value subscription
+     * @param enabled determines if the subscription get (un)subscribed
+     * @param view view of the dialog
+     */
+    private fun setCustomBrightness(enabled: Boolean, view: View) {
+        if (enabled) {
+            customBrightnessSubscription = preferences.customBrightnessValue().asObservable()
+                    .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
+                    .subscribe { setCustomBrightnessValue(it, view) }
+
+            subscriptions.add(customBrightnessSubscription)
+        } else {
+            customBrightnessSubscription?.let { subscriptions.remove(it) }
+            setCustomBrightnessValue(0, view, true)
+        }
+        setCustomBrightnessSeekBar(enabled, view)
+    }
+
+    /**
+     * Sets the brightness of the screen. Range is [-75, 100].
+     * From -75 to -1 a semi-transparent black view is shown at the top with the minimum brightness.
+     * From 1 to 100 it sets that value as brightness.
+     * 0 sets system brightness and hides the overlay.
+     */
+    private fun setCustomBrightnessValue(value: Int, view: View, isDisabled: Boolean = false) = with(view) {
+        // Set black overlay visibility.
+        if (value < 0) {
+            brightness_overlay.visibility = View.VISIBLE
+            val alpha = (Math.abs(value) * 2.56).toInt()
+            brightness_overlay.setBackgroundColor(Color.argb(alpha, 0, 0, 0))
+        } else {
+            brightness_overlay.visibility = View.GONE
+        }
+
+        if (!isDisabled)
+            txt_brightness_seekbar_value.text = value.toString()
+    }
+
+    /**
+     * Manages the color filter value subscription
+     * @param enabled determines if the subscription get (un)subscribed
+     * @param view view of the dialog
+     */
+    private fun setColorFilter(enabled: Boolean, view: View) {
+        if (enabled) {
+            customFilterColorSubscription = preferences.colorFilterValue().asObservable()
+                    .sample(100, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
+                    .subscribe { setColorFilterValue(it, view) }
+
+            subscriptions.add(customFilterColorSubscription)
+        } else {
+            customFilterColorSubscription?.let { subscriptions.remove(it) }
+            view.color_overlay.visibility = View.GONE
+        }
+        setColorFilterSeekBar(enabled, view)
+    }
+
+    /**
+     * Sets the color filter overlay of the screen. Determined by HEX of integer
+     * @param color hex of color.
+     * @param view view of the dialog
+     */
+    private fun setColorFilterValue(@ColorInt color: Int, view: View) = with(view) {
+        color_overlay.visibility = View.VISIBLE
+        color_overlay.setBackgroundColor(color)
+        setValues(color, view)
+    }
+
+    /**
+     * Updates the color value in preference
+     * @param color value of color range [0,255]
+     * @param mask contains hex mask of chosen color
+     * @param bitShift amounts of bits that gets shifted to receive value
+     */
+    fun setColorValue(color: Int, mask: Long, bitShift: Int) {
+        val currentColor = preferences.colorFilterValue().getOrDefault()
+        val updatedColor = (color shl bitShift) or (currentColor and mask.inv().toInt())
+        preferences.colorFilterValue().set(updatedColor)
+    }
+
+    /**
+     * Returns the alpha value from the Color Hex
+     * @param color color hex as int
+     * @return alpha of color
+     */
+    fun getAlphaFromColor(color: Int): Int {
+        return color shr 24 and 0xFF
+    }
+
+    /**
+     * Returns the red value from the Color Hex
+     * @param color color hex as int
+     * @return red of color
+     */
+    fun getRedFromColor(color: Int): Int {
+        return color shr 16 and 0xFF
+    }
+
+    /**
+     * Returns the green value from the Color Hex
+     * @param color color hex as int
+     * @return green of color
+     */
+    fun getGreenFromColor(color: Int): Int {
+        return color shr 8 and 0xFF
+    }
+
+    /**
+     * Returns the blue value from the Color Hex
+     * @param color color hex as int
+     * @return blue of color
+     */
+    fun getBlueFromColor(color: Int): Int {
+        return color and 0xFF
+    }
+
+    /**
+     * Called when dialog is dismissed
+     */
+    override fun onDestroyView() {
+        subscriptions.unsubscribe()
+        super.onDestroyView()
+    }
+
+}

+ 0 - 19
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderSettingsDialog.kt

@@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
 import eu.kanade.tachiyomi.util.plusAssign
 import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
 import kotlinx.android.synthetic.main.dialog_reader_settings.view.*
-import org.adw.library.widgets.discreteseekbar.DiscreteSeekBar
 import rx.Observable
 import rx.android.schedulers.AndroidSchedulers
 import rx.subscriptions.CompositeSubscription
@@ -84,24 +83,6 @@ class ReaderSettingsDialog : DialogFragment() {
         fullscreen.setOnCheckedChangeListener { v, isChecked ->
             preferences.fullscreen().set(isChecked)
         }
-
-        custom_brightness.isChecked = preferences.customBrightness().getOrDefault()
-        custom_brightness.setOnCheckedChangeListener { v, isChecked ->
-            preferences.customBrightness().set(isChecked)
-        }
-
-        brightness_seekbar.progress = preferences.customBrightnessValue().getOrDefault()
-        brightness_seekbar.setOnProgressChangeListener(object : DiscreteSeekBar.OnProgressChangeListener {
-            override fun onProgressChanged(seekBar: DiscreteSeekBar, value: Int, fromUser: Boolean) {
-                if (fromUser) {
-                    preferences.customBrightnessValue().set(value)
-                }
-            }
-
-            override fun onStartTrackingTouch(seekBar: DiscreteSeekBar) {}
-
-            override fun onStopTrackingTouch(seekBar: DiscreteSeekBar) {}
-        })
     }
 
     override fun onDestroyView() {

+ 61 - 0
app/src/main/java/eu/kanade/tachiyomi/widget/NegativeSeekBar.kt

@@ -0,0 +1,61 @@
+package eu.kanade.tachiyomi.widget
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.SeekBar
+import eu.kanade.tachiyomi.R
+
+
+class NegativeSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+        SeekBar(context, attrs) {
+
+    private var minValue: Int = 0
+    private var maxValue: Int = 0
+    private var listener: OnSeekBarChangeListener? = null
+
+    init {
+        val styledAttributes = context.obtainStyledAttributes(
+                attrs,
+                R.styleable.NegativeSeekBar, 0, 0)
+
+        try {
+            setMinSeek(styledAttributes.getInt(R.styleable.NegativeSeekBar_min_seek, 0))
+            setMaxSeek(styledAttributes.getInt(R.styleable.NegativeSeekBar_max_seek, 0))
+        } finally {
+            styledAttributes.recycle()
+        }
+
+        super.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
+            override fun onProgressChanged(seekBar: SeekBar?, value: Int, fromUser: Boolean) {
+                listener?.let { it.onProgressChanged(seekBar, minValue + value, fromUser) }
+            }
+
+            override fun onStartTrackingTouch(p0: SeekBar?) {
+                listener?.let { it.onStartTrackingTouch(p0) }
+            }
+
+            override fun onStopTrackingTouch(p0: SeekBar?) {
+                listener?.let { it.onStopTrackingTouch(p0) }
+            }
+        })
+    }
+
+    override fun setProgress(progress: Int) {
+        super.setProgress(Math.abs(minValue) + progress)
+    }
+
+    fun setMinSeek(minValue: Int) {
+        this.minValue = minValue
+        max = (this.maxValue - this.minValue)
+    }
+
+    fun setMaxSeek(maxValue: Int) {
+        this.maxValue = maxValue
+        max = (this.maxValue - this.minValue)
+    }
+
+    override fun setOnSeekBarChangeListener(listener: OnSeekBarChangeListener?) {
+        this.listener = listener
+    }
+
+}

+ 6 - 4
app/src/main/java/eu/kanade/tachiyomi/widget/SimpleSeekBarListener.kt

@@ -1,11 +1,13 @@
 package eu.kanade.tachiyomi.widget
-
 import android.widget.SeekBar
 
 open class SimpleSeekBarListener : SeekBar.OnSeekBarChangeListener {
-    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {}
+    override fun onProgressChanged(seekBar: SeekBar, value: Int, fromUser: Boolean) {
+    }
 
-    override fun onStartTrackingTouch(seekBar: SeekBar) {}
+    override fun onStartTrackingTouch(seekBar: SeekBar) {
+    }
 
-    override fun onStopTrackingTouch(seekBar: SeekBar) {}
+    override fun onStopTrackingTouch(seekBar: SeekBar) {
+    }
 }

二进制
app/src/main/res/drawable/filter_mock.png


+ 9 - 0
app/src/main/res/drawable/ic_brightness_4_white_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69L23.31,12 20,8.69zM12,18c-0.89,0 -1.74,-0.2 -2.5,-0.55C11.56,16.5 13,14.42 13,12s-1.44,-4.5 -3.5,-5.45C10.26,6.2 11.11,6 12,6c3.31,0 6,2.69 6,6s-2.69,6 -6,6z"/>
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_brightness_5_black_24dp.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M20,15.31L23.31,12 20,8.69V4h-4.69L12,0.69 8.69,4H4v4.69L0.69,12 4,15.31V20h4.69L12,23.31 15.31,20H20v-4.69zM12,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
+</vector>

+ 6 - 0
app/src/main/res/layout/activity_reader.xml

@@ -105,4 +105,10 @@
         android:layout_height="match_parent"
         android:visibility="gone"/>
 
+    <View
+        android:id="@+id/color_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"/>
+
 </FrameLayout>

+ 257 - 0
app/src/main/res/layout/dialog_reader_custom_filter.xml

@@ -0,0 +1,257 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <android.support.v7.widget.AppCompatImageView
+            android:layout_width="wrap_content"
+            android:layout_height="200dp"
+            android:scaleType="centerCrop"
+            android:src="@drawable/filter_mock" />
+
+        <View
+            android:id="@+id/brightness_overlay"
+            android:layout_width="match_parent"
+            android:layout_height="200dp"
+            android:visibility="gone" />
+
+        <View
+            android:id="@+id/color_overlay"
+            android:layout_width="match_parent"
+            android:layout_height="200dp"
+            android:visibility="gone" />
+    </RelativeLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:padding="@dimen/material_component_dialogs_padding_around_content_area">
+
+        <android.support.v7.widget.SwitchCompat
+            android:id="@+id/switch_color_filter"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/pref_custom_color_filter" />
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:id="@+id/txt_color_filter_red_symbol"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_centerVertical="true"
+                android:text="@string/color_filter_r_value"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <TextView
+                android:id="@+id/txt_color_filter_red_value"
+                android:layout_width="30dp"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <SeekBar
+                android:id="@+id/seekbar_color_filter_red"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginEnd="8dp"
+                android:layout_marginLeft="8dp"
+                android:layout_marginRight="8dp"
+                android:layout_marginStart="8dp"
+                android:layout_toEndOf="@id/txt_color_filter_red_symbol"
+                android:layout_toLeftOf="@id/txt_color_filter_red_value"
+                android:layout_toRightOf="@id/txt_color_filter_red_symbol"
+                android:layout_toStartOf="@id/txt_color_filter_red_value"
+                android:max="255"
+                android:padding="@dimen/material_component_text_fields_floating_label_padding_between_label_and_input_text" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:id="@+id/txt_color_filter_green_symbol"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_centerVertical="true"
+                android:text="@string/color_filter_g_value"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <TextView
+                android:id="@+id/txt_color_filter_green_value"
+                android:layout_width="30dp"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <SeekBar
+                android:id="@+id/seekbar_color_filter_green"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginEnd="8dp"
+                android:layout_marginLeft="8dp"
+                android:layout_marginRight="8dp"
+                android:layout_marginStart="8dp"
+                android:layout_toEndOf="@id/txt_color_filter_green_symbol"
+                android:layout_toLeftOf="@id/txt_color_filter_green_value"
+                android:layout_toRightOf="@id/txt_color_filter_green_symbol"
+                android:layout_toStartOf="@id/txt_color_filter_green_value"
+                android:max="255"
+                android:padding="@dimen/material_component_text_fields_floating_label_padding_between_label_and_input_text" />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:id="@+id/txt_color_filter_blue_symbol"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_centerVertical="true"
+                android:text="@string/color_filter_b_value"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <TextView
+                android:id="@+id/txt_color_filter_blue_value"
+                android:layout_width="30dp"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <SeekBar
+                android:id="@+id/seekbar_color_filter_blue"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginEnd="8dp"
+                android:layout_marginLeft="8dp"
+                android:layout_marginRight="8dp"
+                android:layout_marginStart="8dp"
+                android:layout_toEndOf="@id/txt_color_filter_blue_symbol"
+                android:layout_toLeftOf="@id/txt_color_filter_blue_value"
+                android:layout_toRightOf="@id/txt_color_filter_blue_symbol"
+                android:layout_toStartOf="@id/txt_color_filter_blue_value"
+                android:max="255"
+                android:padding="@dimen/material_component_text_fields_floating_label_padding_between_label_and_input_text" />
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <TextView
+                android:id="@+id/txt_color_filter_alpha_symbol"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_centerVertical="true"
+                android:text="@string/color_filter_a_value"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <TextView
+                android:id="@+id/txt_color_filter_alpha_value"
+                android:layout_width="30dp"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <SeekBar
+                android:id="@+id/seekbar_color_filter_alpha"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginEnd="8dp"
+                android:layout_marginLeft="8dp"
+                android:layout_marginRight="8dp"
+                android:layout_marginStart="8dp"
+                android:layout_toEndOf="@id/txt_color_filter_alpha_symbol"
+                android:layout_toLeftOf="@id/txt_color_filter_alpha_value"
+                android:layout_toRightOf="@id/txt_color_filter_alpha_symbol"
+                android:layout_toStartOf="@id/txt_color_filter_alpha_value"
+                android:max="255"
+                android:padding="@dimen/material_component_text_fields_floating_label_padding_between_label_and_input_text" />
+
+        </RelativeLayout>
+
+        <android.support.v7.widget.SwitchCompat
+            android:id="@+id/custom_brightness"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="@dimen/material_component_cards_primary_title_top_padding"
+            android:text="@string/pref_custom_brightness" />
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <android.support.v7.widget.AppCompatImageView
+                android:id="@+id/txt_brightness_seekbar_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_centerVertical="true"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary"
+                android:tint="?android:attr/textColorSecondary"
+                app:srcCompat="@drawable/ic_brightness_5_black_24dp" />
+
+            <TextView
+                android:id="@+id/txt_brightness_seekbar_value"
+                android:layout_width="30dp"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_alignParentRight="true"
+                android:layout_centerVertical="true"
+                android:textAppearance="@style/TextAppearance.Regular.SubHeading.Secondary" />
+
+            <eu.kanade.tachiyomi.widget.NegativeSeekBar
+                android:id="@+id/brightness_seekbar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerVertical="true"
+                android:layout_marginEnd="8dp"
+                android:layout_marginLeft="8dp"
+                android:layout_marginRight="8dp"
+                android:layout_marginStart="8dp"
+                android:layout_toEndOf="@id/txt_brightness_seekbar_icon"
+                android:layout_toLeftOf="@id/txt_brightness_seekbar_value"
+                android:layout_toRightOf="@id/txt_brightness_seekbar_icon"
+                android:layout_toStartOf="@id/txt_brightness_seekbar_value"
+                android:padding="@dimen/material_component_text_fields_floating_label_padding_between_label_and_input_text"
+                app:max_seek="100"
+                app:min_seek="-75" />
+
+        </RelativeLayout>
+    </LinearLayout>
+
+</LinearLayout>

+ 2 - 19
app/src/main/res/layout/dialog_reader_settings.xml

@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    xmlns:setting="http://schemas.android.com/tools"
     android:orientation="vertical"
     android:padding="@dimen/material_component_dialogs_padding_around_content_area"
     android:divider="@drawable/empty_divider"
@@ -171,21 +171,4 @@
         android:layout_height="wrap_content"
         android:text="@string/pref_fullscreen"/>
 
-    <android.support.v7.widget.SwitchCompat
-        android:id="@+id/custom_brightness"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/pref_custom_brightness"/>
-
-    <org.adw.library.widgets.discreteseekbar.DiscreteSeekBar
-        android:id="@+id/brightness_seekbar"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        app:dsb_min="-75"
-        app:dsb_max="100"
-        app:dsb_indicatorFormatter="%d%%"
-        app:dsb_indicatorTextAppearance="@style/TextAppearance.Regular"
-        app:dsb_indicatorColor="?colorAccent"
-        app:dsb_progressColor="?colorAccent" />
-
 </LinearLayout>

+ 8 - 1
app/src/main/res/menu/reader.xml

@@ -2,11 +2,18 @@
 <menu xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns:app="http://schemas.android.com/apk/res-auto">
 
+    <item
+        android:id="@+id/action_custom_filter"
+        android:title="@string/label_settings"
+        android:icon="@drawable/ic_brightness_4_white_24dp"
+        app:showAsAction="ifRoom"
+        />
+
     <item
         android:id="@+id/action_settings"
         android:title="@string/label_settings"
         android:icon="@drawable/ic_settings_white_24dp"
-        app:showAsAction="always"
+        app:showAsAction="ifRoom"
         />
 
 </menu>

+ 5 - 0
app/src/main/res/values/attrs.xml

@@ -17,6 +17,11 @@
         <attr name="max" format="integer"/>
     </declare-styleable>
 
+    <declare-styleable name="NegativeSeekBar">
+        <attr name="min_seek" format="integer"/>
+        <attr name="max_seek" format="integer"/>
+    </declare-styleable>
+
     <attr name="navigation_view_theme" format="reference"/>
     <attr name="selectable_list_drawable" format="reference|integer" />
     <attr name="selectable_library_drawable" format="reference|integer"/>

+ 3 - 0
app/src/main/res/values/keys.xml

@@ -32,6 +32,9 @@
     <string name="pref_keep_screen_on_key">pref_keep_screen_on_key</string>
     <string name="pref_custom_brightness_key">pref_custom_brightness_key</string>
     <string name="pref_custom_brightness_value_key">custom_brightness_value</string>
+    <string name="pref_color_filter_key">pref_color_filter_key</string>
+    <string name="pref_color_filter_value_key">color_filter_value</string>
+    <string name="pref_red_filter_value_key">pref_red_filter_value</string>
     <string name="pref_reader_theme_key">pref_reader_theme_key</string>
     <string name="pref_image_decoder_key">pref_image_decoder_key</string>
     <string name="pref_read_with_volume_keys_key">reader_volume_keys</string>

+ 6 - 0
app/src/main/res/values/strings.xml

@@ -105,6 +105,7 @@
     <string name="pref_enable_transitions">Enable transitions</string>
     <string name="pref_show_page_number">Show page number</string>
     <string name="pref_custom_brightness">Use custom brightness</string>
+    <string name="pref_custom_color_filter">Use custom color filter</string>
     <string name="pref_keep_screen_on">Keep screen on</string>
     <string name="pref_reader_navigation">Navigation</string>
     <string name="pref_read_with_volume_keys">Volume keys</string>
@@ -138,6 +139,11 @@
     <string name="rotation_lock">Lock</string>
     <string name="rotation_force_portrait">Force portrait</string>
     <string name="rotation_force_landscape">Force landscape</string>
+    <string name="color_filter_r_value">R</string>
+    <string name="color_filter_g_value">G</string>
+    <string name="color_filter_b_value">B</string>
+    <string name="color_filter_a_value">A</string>
+
 
       <!-- Downloads section -->
     <string name="pref_download_directory">Downloads directory</string>

+ 8 - 0
app/src/main/res/values/styles.xml

@@ -91,6 +91,10 @@
         <item name="android:textSize">16sp</item>
     </style>
 
+    <style name="TextAppearance.Regular.SubHeading.Secondary">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    </style>
+
     <style name="TextAppearance.Regular.SubHeading.Light">
         <item name="android:textColor">@color/textColorPrimaryDark</item>
     </style>
@@ -107,6 +111,10 @@
         <item name="android:textSize">20sp</item>
     </style>
 
+    <style name="TextAppearance.Medium.Title.Secondary">
+        <item name="android:textColor">?android:attr/textColorSecondary</item>
+    </style>
+
     <style name="TextAppearance.Medium.Body2">
         <item name="android:textSize">14sp</item>
     </style>

+ 0 - 5
app/src/main/res/xml/pref_reader.xml

@@ -69,11 +69,6 @@
             android:key="@string/pref_show_page_number_key"
             android:defaultValue="true" />
 
-        <SwitchPreference
-            android:title="@string/pref_custom_brightness"
-            android:key="@string/pref_custom_brightness_key"
-            android:defaultValue="false" />
-
         <SwitchPreference
             android:title="@string/pref_keep_screen_on"
             android:key="@string/pref_keep_screen_on_key"