Convert rotation to FlowPreference, remove some unused subscriptions code
Also remove EH lock code(was broken because of RxController changes) (cherry picked from commit d46a742a43d23c62aecf203e21a5221a06195131) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
This commit is contained in:
parent
e4e069ccca
commit
ef3f4c2e17
@ -92,7 +92,7 @@ class PreferencesHelper(val context: Context) {
|
||||
|
||||
fun themeDark() = flowPrefs.getString(Keys.themeDark, Values.THEME_DARK_DEFAULT)
|
||||
|
||||
fun rotation() = rxPrefs.getInteger(Keys.rotation, 1)
|
||||
fun rotation() = flowPrefs.getInt(Keys.rotation, 1)
|
||||
|
||||
fun pageTransitions() = flowPrefs.getBoolean(Keys.enableTransitions, true)
|
||||
|
||||
|
@ -10,25 +10,7 @@ import rx.subscriptions.CompositeSubscription
|
||||
|
||||
abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseController<VB>(bundle) {
|
||||
|
||||
var untilDetachSubscriptions = CompositeSubscription()
|
||||
private set
|
||||
|
||||
var untilDestroySubscriptions = CompositeSubscription()
|
||||
private set
|
||||
|
||||
@CallSuper
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
if (untilDetachSubscriptions.isUnsubscribed) {
|
||||
untilDetachSubscriptions = CompositeSubscription()
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
untilDetachSubscriptions.unsubscribe()
|
||||
}
|
||||
private var untilDestroySubscriptions = CompositeSubscription()
|
||||
|
||||
@CallSuper
|
||||
override fun onViewCreated(view: View) {
|
||||
@ -43,49 +25,7 @@ abstract class RxController<VB : ViewBinding>(bundle: Bundle? = null) : BaseCont
|
||||
untilDestroySubscriptions.unsubscribe()
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDetach(): Subscription {
|
||||
return subscribe().also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDetach(onNext: (T) -> Unit): Subscription {
|
||||
return subscribe(onNext).also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDetach(
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit
|
||||
): Subscription {
|
||||
return subscribe(onNext, onError).also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDetach(
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit,
|
||||
onCompleted: () -> Unit
|
||||
): Subscription {
|
||||
return subscribe(onNext, onError, onCompleted).also { untilDetachSubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDestroy(): Subscription {
|
||||
return subscribe().also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
|
||||
return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDestroy(
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit
|
||||
): Subscription {
|
||||
return subscribe(onNext, onError).also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
|
||||
fun <T> Observable<T>.subscribeUntilDestroy(
|
||||
onNext: (T) -> Unit,
|
||||
onError: (Throwable) -> Unit,
|
||||
onCompleted: () -> Unit
|
||||
): Subscription {
|
||||
return subscribe(onNext, onError, onCompleted).also { untilDestroySubscriptions.add(it) }
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.asImmediateFlow
|
||||
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
@ -64,6 +65,7 @@ import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.roundToLong
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.observeOn
|
||||
@ -226,7 +228,6 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
super.onDestroy()
|
||||
viewer?.destroy()
|
||||
viewer = null
|
||||
config?.destroy()
|
||||
config = null
|
||||
progressDialog?.dismiss()
|
||||
progressDialog = null
|
||||
@ -852,22 +853,17 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
*/
|
||||
private inner class ReaderConfig {
|
||||
|
||||
/**
|
||||
* List of subscriptions to keep while the reader is alive.
|
||||
*/
|
||||
private val subscriptions = CompositeSubscription()
|
||||
|
||||
/**
|
||||
* Initializes the reader subscriptions.
|
||||
*/
|
||||
init {
|
||||
val sharedRotation = preferences.rotation().asObservable().share()
|
||||
val initialRotation = sharedRotation.take(1)
|
||||
val rotationUpdates = sharedRotation.skip(1)
|
||||
.delay(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
|
||||
|
||||
subscriptions += Observable.merge(initialRotation, rotationUpdates)
|
||||
.subscribe { setOrientation(it) }
|
||||
preferences.rotation().asImmediateFlow { setOrientation(it) }
|
||||
.drop(1)
|
||||
.onEach {
|
||||
delay(250)
|
||||
setOrientation(it)
|
||||
}
|
||||
.launchIn(scope)
|
||||
|
||||
preferences.readerTheme().asFlow()
|
||||
.drop(1) // We only care about updates
|
||||
@ -905,13 +901,6 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the reader is being destroyed. It cleans up all the subscriptions.
|
||||
*/
|
||||
fun destroy() {
|
||||
subscriptions.unsubscribe()
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the user preferred [orientation] on the activity.
|
||||
*/
|
||||
|
@ -1,158 +0,0 @@
|
||||
package exh.ui.lock
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.github.ajalt.reprint.core.AuthenticationResult
|
||||
import com.github.ajalt.reprint.core.Reprint
|
||||
import com.github.ajalt.reprint.rxjava.RxReprint
|
||||
import com.mattprecious.swirl.SwirlView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.util.preference.onChange
|
||||
import exh.util.dpToPx
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
SwitchPreferenceCompat(context, attrs) {
|
||||
|
||||
val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
val fingerprintSupported
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
Reprint.isHardwarePresent() &&
|
||||
Reprint.hasFingerprintRegistered()
|
||||
|
||||
val useFingerprint
|
||||
get() = fingerprintSupported &&
|
||||
prefs.eh_lockUseFingerprint().getOrDefault()
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun onAttached() {
|
||||
super.onAttached()
|
||||
if (fingerprintSupported) {
|
||||
updateSummary()
|
||||
onChange {
|
||||
if (it as Boolean) {
|
||||
tryChange()
|
||||
} else {
|
||||
prefs.eh_lockUseFingerprint().set(false)
|
||||
}
|
||||
!it
|
||||
}
|
||||
} else {
|
||||
title = "Fingerprint unsupported"
|
||||
shouldDisableView = true
|
||||
summary = if (!Reprint.hasFingerprintRegistered()) {
|
||||
"No fingerprints enrolled!"
|
||||
} else {
|
||||
"Fingerprint unlock is unsupported on this device!"
|
||||
}
|
||||
onChange { false }
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSummary() {
|
||||
isChecked = useFingerprint
|
||||
title = if (isChecked) {
|
||||
"Fingerprint enabled"
|
||||
} else {
|
||||
"Fingerprint disabled"
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
fun tryChange() {
|
||||
val statusTextView = TextView(context).apply {
|
||||
text = "Please touch the fingerprint sensor"
|
||||
val size = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
layoutParams = (
|
||||
layoutParams ?: ViewGroup.LayoutParams(
|
||||
size, size
|
||||
)
|
||||
).apply {
|
||||
width = size
|
||||
height = size
|
||||
setPadding(0, 0, dpToPx(context, 8), 0)
|
||||
}
|
||||
}
|
||||
val iconView = SwirlView(context).apply {
|
||||
val size = dpToPx(context, 30)
|
||||
layoutParams = (
|
||||
layoutParams ?: ViewGroup.LayoutParams(
|
||||
size, size
|
||||
)
|
||||
).apply {
|
||||
width = size
|
||||
height = size
|
||||
}
|
||||
setState(SwirlView.State.OFF, false)
|
||||
}
|
||||
val linearLayout = LinearLayoutCompat(context).apply {
|
||||
orientation = LinearLayoutCompat.HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT
|
||||
layoutParams = (
|
||||
layoutParams ?: LinearLayoutCompat.LayoutParams(
|
||||
size, size
|
||||
)
|
||||
).apply {
|
||||
width = size
|
||||
height = size
|
||||
val pSize = dpToPx(context, 24)
|
||||
setPadding(pSize, 0, pSize, 0)
|
||||
}
|
||||
|
||||
addView(statusTextView)
|
||||
addView(iconView)
|
||||
}
|
||||
val dialog = MaterialDialog(context)
|
||||
.title(text = "Fingerprint verification")
|
||||
.customView(view = linearLayout)
|
||||
.negativeButton(R.string.action_cancel)
|
||||
.cancelable(true)
|
||||
.cancelOnTouchOutside(true)
|
||||
dialog.show()
|
||||
iconView.setState(SwirlView.State.ON)
|
||||
val subscription = RxReprint.authenticate()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { result ->
|
||||
when (result.status) {
|
||||
AuthenticationResult.Status.SUCCESS -> {
|
||||
iconView.setState(SwirlView.State.ON)
|
||||
prefs.eh_lockUseFingerprint().set(true)
|
||||
dialog.dismiss()
|
||||
updateSummary()
|
||||
}
|
||||
AuthenticationResult.Status.NONFATAL_FAILURE -> {
|
||||
iconView.setState(SwirlView.State.ERROR)
|
||||
statusTextView.text = result.errorMessage
|
||||
}
|
||||
AuthenticationResult.Status.FATAL_FAILURE, null -> {
|
||||
MaterialDialog(context)
|
||||
.title(text = "Fingerprint verification failed!")
|
||||
.message(text = result.errorMessage)
|
||||
.positiveButton(android.R.string.ok)
|
||||
.cancelable(true)
|
||||
.cancelOnTouchOutside(false)
|
||||
.show()
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
dialog.setOnDismissListener {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package exh.ui.lock
|
||||
|
||||
import android.view.WindowManager
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
object LockActivityDelegate {
|
||||
private val preferences by injectLazy<PreferencesHelper>()
|
||||
|
||||
var willLock: Boolean = true
|
||||
|
||||
private val uiScope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
fun doLock(router: Router, animate: Boolean = false) {
|
||||
router.pushController(
|
||||
RouterTransaction.with(LockController())
|
||||
.popChangeHandler(LockChangeHandler(animate))
|
||||
)
|
||||
}
|
||||
|
||||
fun onCreate(activity: FragmentActivity) {
|
||||
preferences.secureScreen().asFlow()
|
||||
.onEach {
|
||||
if (it) {
|
||||
activity.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
}
|
||||
}
|
||||
.launchIn(uiScope)
|
||||
}
|
||||
|
||||
fun onResume(activity: FragmentActivity, router: Router) {
|
||||
if (lockEnabled() && !isAppLocked(router) && willLock && !preferences.eh_lockManually().getOrDefault()) {
|
||||
doLock(router)
|
||||
willLock = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAppLocked(router: Router): Boolean {
|
||||
return router.backstack.lastOrNull()?.controller() is LockController
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
package exh.ui.lock
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.changehandler.AnimatorChangeHandler
|
||||
import java.util.ArrayList
|
||||
|
||||
class LockChangeHandler : AnimatorChangeHandler {
|
||||
constructor() : super()
|
||||
|
||||
constructor(removesFromViewOnPush: Boolean) : super(removesFromViewOnPush)
|
||||
|
||||
constructor(duration: Long) : super(duration)
|
||||
|
||||
constructor(duration: Long, removesFromViewOnPush: Boolean) : super(duration, removesFromViewOnPush)
|
||||
|
||||
override fun getAnimator(container: ViewGroup, from: View?, to: View?, isPush: Boolean, toAddedToContainer: Boolean): Animator {
|
||||
val animator = AnimatorSet()
|
||||
val viewAnimators = ArrayList<Animator>()
|
||||
|
||||
if (!isPush && from != null) {
|
||||
viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_X, 3f))
|
||||
viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_Y, 3f))
|
||||
viewAnimators.add(ObjectAnimator.ofFloat(from, View.ALPHA, 0f))
|
||||
}
|
||||
|
||||
animator.playTogether(viewAnimators)
|
||||
return animator
|
||||
}
|
||||
|
||||
override fun resetFromView(from: View) {}
|
||||
|
||||
override fun copy(): ControllerChangeHandler =
|
||||
LockChangeHandler(animationDuration, removesFromViewOnPush())
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
package exh.ui.lock
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.andrognito.pinlockview.PinLockListener
|
||||
import com.github.ajalt.reprint.core.AuthenticationResult
|
||||
import com.github.ajalt.reprint.rxjava.RxReprint
|
||||
import com.mattprecious.swirl.SwirlView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.databinding.ActivityLockBinding
|
||||
import eu.kanade.tachiyomi.ui.base.controller.NucleusController
|
||||
import exh.util.dpToPx
|
||||
import kotlinx.android.synthetic.main.activity_lock.view.swirl_container
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LockController : NucleusController<ActivityLockBinding, LockPresenter>() {
|
||||
|
||||
val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
binding = ActivityLockBinding.inflate(inflater)
|
||||
return binding.root
|
||||
}
|
||||
override fun createPresenter() = LockPresenter()
|
||||
|
||||
override fun getTitle() = "Application locked"
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
super.onViewCreated(view)
|
||||
|
||||
if (!lockEnabled(prefs)) {
|
||||
closeLock()
|
||||
return
|
||||
}
|
||||
|
||||
with(view) {
|
||||
// Setup pin lock
|
||||
binding.pinLockView.attachIndicatorDots(binding.indicatorDots)
|
||||
|
||||
binding.pinLockView.pinLength = prefs.eh_lockLength().getOrDefault()
|
||||
binding.pinLockView.setPinLockListener(object : PinLockListener {
|
||||
override fun onEmpty() {}
|
||||
|
||||
override fun onComplete(pin: String) {
|
||||
if (sha512(pin, prefs.eh_lockSalt().get()!!) == prefs.eh_lockHash().get()) {
|
||||
// Yay!
|
||||
closeLock()
|
||||
} else {
|
||||
MaterialDialog(context)
|
||||
.title(text = "PIN code incorrect")
|
||||
.message(text = "The PIN code you entered is incorrect. Please try again.")
|
||||
.cancelable(true)
|
||||
.cancelOnTouchOutside(true)
|
||||
.positiveButton(android.R.string.ok)
|
||||
.show()
|
||||
binding.pinLockView.resetPinLockView()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPinChange(pinLength: Int, intermediatePin: String?) {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
|
||||
with(view) {
|
||||
// Fingerprint
|
||||
if (presenter.useFingerprint) {
|
||||
binding.swirlContainer.visibility = View.VISIBLE
|
||||
binding.swirlContainer.removeAllViews()
|
||||
val icon = SwirlView(context).apply {
|
||||
val size = dpToPx(context, 60)
|
||||
layoutParams = (
|
||||
layoutParams ?: ViewGroup.LayoutParams(
|
||||
size, size
|
||||
)
|
||||
).apply {
|
||||
width = size
|
||||
height = size
|
||||
|
||||
val pSize = dpToPx(context, 8)
|
||||
setPadding(pSize, pSize, pSize, pSize)
|
||||
}
|
||||
val lockColor = resolvColor(android.R.attr.windowBackground)
|
||||
setBackgroundColor(lockColor)
|
||||
val bgColor = resolvColor(android.R.attr.colorBackground)
|
||||
// Disable elevation if lock color is same as background color
|
||||
if (lockColor == bgColor) {
|
||||
this@with.swirl_container.cardElevation = 0f
|
||||
}
|
||||
setState(SwirlView.State.OFF, true)
|
||||
}
|
||||
binding.swirlContainer.addView(icon)
|
||||
icon.setState(SwirlView.State.ON)
|
||||
RxReprint.authenticate()
|
||||
.subscribeUntilDetach {
|
||||
when (it.status) {
|
||||
AuthenticationResult.Status.SUCCESS -> closeLock()
|
||||
AuthenticationResult.Status.NONFATAL_FAILURE -> icon.setState(SwirlView.State.ERROR)
|
||||
AuthenticationResult.Status.FATAL_FAILURE, null -> {
|
||||
MaterialDialog(context)
|
||||
.title(text = "Fingerprint error!")
|
||||
.message(text = it.errorMessage)
|
||||
.cancelable(false)
|
||||
.cancelOnTouchOutside(false)
|
||||
.positiveButton(android.R.string.ok)
|
||||
.show()
|
||||
icon.setState(SwirlView.State.OFF)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.swirlContainer.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolvColor(color: Int): Int {
|
||||
val typedVal = TypedValue()
|
||||
activity!!.theme!!.resolveAttribute(color, typedVal, true)
|
||||
return typedVal.data
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
}
|
||||
|
||||
fun closeLock() {
|
||||
router.popCurrentController()
|
||||
}
|
||||
|
||||
override fun handleBack() = true
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package exh.ui.lock
|
||||
|
||||
import android.content.Context
|
||||
import android.text.InputType
|
||||
import android.util.AttributeSet
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.input.input
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.util.preference.onChange
|
||||
import java.math.BigInteger
|
||||
import java.security.SecureRandom
|
||||
import rx.Observable
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||
SwitchPreferenceCompat(context, attrs) {
|
||||
|
||||
private val secureRandom by lazy { SecureRandom() }
|
||||
|
||||
val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
override fun onAttached() {
|
||||
super.onAttached()
|
||||
updateSummary()
|
||||
onChange {
|
||||
tryChange()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSummary() {
|
||||
isChecked = lockEnabled(prefs)
|
||||
if (isChecked) {
|
||||
title = "Lock enabled"
|
||||
summary = "Tap to disable or change pin code"
|
||||
} else {
|
||||
title = "Lock disabled"
|
||||
summary = "Tap to enable"
|
||||
}
|
||||
}
|
||||
|
||||
fun tryChange() {
|
||||
if (!notifyLockSecurity(context)) {
|
||||
MaterialDialog(context)
|
||||
.title(text = "Lock application")
|
||||
.message(text = "Enter a pin to lock the application. Enter nothing to disable the pin lock.")
|
||||
// .inputRangeRes(0, 10, R.color.material_red_500)
|
||||
// .inputType(InputType.TYPE_CLASS_NUMBER)
|
||||
.input(maxLength = 10, inputType = InputType.TYPE_CLASS_NUMBER, allowEmpty = true) { _, c ->
|
||||
val progressDialog = MaterialDialog(context)
|
||||
.title(text = "Saving password")
|
||||
.cancelable(false)
|
||||
progressDialog.show()
|
||||
Observable.fromCallable {
|
||||
savePassword(c.toString())
|
||||
}.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
progressDialog.dismiss()
|
||||
updateSummary()
|
||||
}
|
||||
}
|
||||
.negativeButton(R.string.action_cancel)
|
||||
.cancelable(true)
|
||||
.cancelOnTouchOutside(true)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun savePassword(password: String) {
|
||||
val salt: String?
|
||||
val hash: String?
|
||||
val length: Int
|
||||
if (password.isEmpty()) {
|
||||
salt = null
|
||||
hash = null
|
||||
length = -1
|
||||
} else {
|
||||
salt = BigInteger(130, secureRandom).toString(32)
|
||||
hash = sha512(password, salt)
|
||||
length = password.length
|
||||
}
|
||||
prefs.eh_lockSalt().set(salt)
|
||||
prefs.eh_lockHash().set(hash)
|
||||
prefs.eh_lockLength().set(length)
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package exh.ui.lock
|
||||
|
||||
import android.os.Build
|
||||
import com.github.ajalt.reprint.core.Reprint
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LockPresenter : BasePresenter<LockController>() {
|
||||
val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
val useFingerprint
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
Reprint.isHardwarePresent() &&
|
||||
Reprint.hasFingerprintRegistered() &&
|
||||
prefs.eh_lockUseFingerprint().getOrDefault()
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
package exh.ui.lock
|
||||
|
||||
import android.app.AppOpsManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.provider.Settings
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.elvishew.xlog.XLog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import java.security.MessageDigest
|
||||
import kotlin.experimental.and
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Password hashing utils
|
||||
*/
|
||||
|
||||
/**
|
||||
* Yes, I know SHA512 is fast, but bcrypt on mobile devices is too slow apparently
|
||||
*/
|
||||
fun sha512(passwordToHash: String, salt: String): String {
|
||||
val md = MessageDigest.getInstance("SHA-512")
|
||||
md.update(salt.toByteArray(charset("UTF-8")))
|
||||
val bytes = md.digest(passwordToHash.toByteArray(charset("UTF-8")))
|
||||
val sb = StringBuilder()
|
||||
for (i in bytes.indices) {
|
||||
sb.append(Integer.toString((bytes[i] and 0xff.toByte()) + 0x100, 16).substring(1))
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if lock is enabled
|
||||
*/
|
||||
fun lockEnabled(prefs: PreferencesHelper = Injekt.get()) =
|
||||
prefs.eh_lockHash().get() != null &&
|
||||
prefs.eh_lockSalt().get() != null &&
|
||||
prefs.eh_lockLength().getOrDefault() != -1
|
||||
|
||||
/**
|
||||
* Check if the lock will function properly
|
||||
*
|
||||
* @return true if action is required, false if lock is working properly
|
||||
*/
|
||||
fun notifyLockSecurity(
|
||||
context: Context,
|
||||
prefs: PreferencesHelper = Injekt.get()
|
||||
): Boolean {
|
||||
return false
|
||||
if (!prefs.eh_lockManually().getOrDefault() &&
|
||||
!hasAccessToUsageStats(context)
|
||||
) {
|
||||
MaterialDialog(context)
|
||||
.title(text = "Permission required")
|
||||
.message(
|
||||
text = "${context.getString(R.string.app_name)} requires the usage stats permission to detect when you leave the app. " +
|
||||
"This is required for the application lock to function properly. " +
|
||||
"Press OK to grant this permission now."
|
||||
)
|
||||
.negativeButton(R.string.action_cancel)
|
||||
.positiveButton(android.R.string.ok) {
|
||||
try {
|
||||
context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!")
|
||||
MaterialDialog(context)
|
||||
.title(text = "Grant permission manually")
|
||||
.message(
|
||||
text = "Failed to launch the window used to grant the usage stats permission. " +
|
||||
"You can still grant this permission manually: go to your phone's settings and search for 'usage access'."
|
||||
)
|
||||
.positiveButton(android.R.string.ok) { it.dismiss() }
|
||||
.cancelable(true)
|
||||
.cancelOnTouchOutside(false)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
.cancelable(false)
|
||||
.show()
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun hasAccessToUsageStats(context: Context): Boolean {
|
||||
return try {
|
||||
val packageManager = context.packageManager
|
||||
val applicationInfo = packageManager.getApplicationInfo(context.packageName, 0)
|
||||
val appOpsManager = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
|
||||
val mode = appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, applicationInfo.uid, applicationInfo.packageName)
|
||||
(mode == AppOpsManager.MODE_ALLOWED)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user