mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 06:17:57 +01:00 
			
		
		
		
	Convert rotation to FlowPreference, remove some unused subscriptions code
Also remove EH lock code(was broken because of RxController changes)
(cherry picked from commit d46a742a43)
# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
			
			
This commit is contained in:
		| @@ -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 | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user