Add fingerprint to lock UI

Migrate login UI to conductor
Fix batch add controller not saving EditText content onResume
Prevent double-locking of lock UI
Remove back button from lock UI
Fix login preference not updating
This commit is contained in:
NerdNumber9 2017-08-24 17:11:43 -04:00
parent 32d02f9329
commit b20b3d6f5c
21 changed files with 438 additions and 109 deletions

View File

@ -219,9 +219,16 @@ dependencies {
//JVE (Regex) (EH) //JVE (Regex) (EH)
compile 'ru.lanwen.verbalregex:java-verbal-expressions:1.4' compile 'ru.lanwen.verbalregex:java-verbal-expressions:1.4'
//Pin lock view //Pin lock view (EXH)
compile 'com.andrognito.pinlockview:pinlockview:1.0.1' compile 'com.andrognito.pinlockview:pinlockview:1.0.1'
//Reprint (EXH)
compile 'com.github.ajalt.reprint:core:3.1.0@aar' // required: supports marshmallow devices
compile 'com.github.ajalt.reprint:rxjava:3.1.0@aar' // optional: the RxJava 1 interface
//Swirl (EXH)
compile 'com.mattprecious.swirl:swirl:1.0.0'
// Tests // Tests
//Paper DB screws up tests //Paper DB screws up tests
/*testCompile 'junit:junit:4.12' /*testCompile 'junit:junit:4.12'

View File

@ -10,6 +10,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.GET_TASKS"/> <uses-permission android:name="android.permission.GET_TASKS"/>
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" /> tools:ignore="ProtectedPermissions" />
@ -106,7 +107,7 @@
<!-- EH --> <!-- EH -->
<activity <activity
android:name="exh.ui.login.LoginActivity" android:name="exh.ui.login.LoginController"
android:label="@string/label_login"> android:label="@string/label_login">
<!-- TODO parent activity --> <!-- TODO parent activity -->
</activity> </activity>

View File

@ -5,6 +5,7 @@ import android.content.Context
import android.content.res.Configuration import android.content.res.Configuration
import android.support.multidex.MultiDex import android.support.multidex.MultiDex
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import com.github.ajalt.reprint.core.Reprint
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob import eu.kanade.tachiyomi.data.updater.UpdateCheckerJob
@ -26,6 +27,7 @@ open class App : Application() {
setupJobManager() setupJobManager()
Paper.init(this) //Setup metadata DB (EH) Paper.init(this) //Setup metadata DB (EH)
Reprint.initialize(this) //Setup fingerprint (EH)
LocaleHelper.updateConfiguration(this, resources.configuration) LocaleHelper.updateConfiguration(this, resources.configuration)
} }

View File

@ -199,5 +199,7 @@ class PreferencesHelper(val context: Context) {
fun lockSalt() = rxPrefs.getString("lock_salt", null) fun lockSalt() = rxPrefs.getString("lock_salt", null)
fun lockLength() = rxPrefs.getInteger("lock_length", -1) fun lockLength() = rxPrefs.getInteger("lock_length", -1)
fun lockUseFingerprint() = rxPrefs.getBoolean("lock_finger", false)
// <-- EH // <-- EH
} }

View File

@ -19,7 +19,7 @@ import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder import java.net.URLEncoder
import java.util.* import java.util.*
import exh.ui.login.LoginActivity import exh.ui.login.LoginController
import exh.util.UriFilter import exh.util.UriFilter
import exh.util.UriGroup import exh.util.UriGroup
import okhttp3.CacheControl import okhttp3.CacheControl
@ -323,9 +323,9 @@ class EHentai(override val id: Long,
val cookiesHeader by lazy { val cookiesHeader by lazy {
val cookies: MutableMap<String, String> = mutableMapOf() val cookies: MutableMap<String, String> = mutableMapOf()
if(prefs.enableExhentai().getOrDefault()) { if(prefs.enableExhentai().getOrDefault()) {
cookies.put(LoginActivity.MEMBER_ID_COOKIE, prefs.memberIdVal().get()!!) cookies.put(LoginController.MEMBER_ID_COOKIE, prefs.memberIdVal().get()!!)
cookies.put(LoginActivity.PASS_HASH_COOKIE, prefs.passHashVal().get()!!) cookies.put(LoginController.PASS_HASH_COOKIE, prefs.passHashVal().get()!!)
cookies.put(LoginActivity.IGNEOUS_COOKIE, prefs.igneousVal().get()!!) cookies.put(LoginController.IGNEOUS_COOKIE, prefs.igneousVal().get()!!)
} }
//Setup settings //Setup settings

View File

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.main
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.app.ActivityManager import android.app.ActivityManager
import android.app.Service import android.app.Service
import android.app.usage.UsageStats
import android.app.usage.UsageStatsManager import android.app.usage.UsageStatsManager
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
@ -15,8 +14,6 @@ import android.support.v7.graphics.drawable.DrawerArrowDrawable
import android.view.ViewGroup import android.view.ViewGroup
import com.bluelinelabs.conductor.* import com.bluelinelabs.conductor.*
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.bluelinelabs.conductor.changehandler.SimpleSwapChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import eu.kanade.tachiyomi.Migrations import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -40,7 +37,6 @@ import exh.ui.lock.lockEnabled
import exh.ui.lock.notifyLockSecurity import exh.ui.lock.notifyLockSecurity
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.*
class MainActivity : BaseActivity() { class MainActivity : BaseActivity() {
@ -49,7 +45,7 @@ class MainActivity : BaseActivity() {
val preferences: PreferencesHelper by injectLazy() val preferences: PreferencesHelper by injectLazy()
private var drawerArrow: DrawerArrowDrawable? = null var drawerArrow: DrawerArrowDrawable? = null
private var secondaryDrawer: ViewGroup? = null private var secondaryDrawer: ViewGroup? = null
@ -153,6 +149,8 @@ class MainActivity : BaseActivity() {
if (savedInstanceState == null) { if (savedInstanceState == null) {
val lockEnabled = lockEnabled(preferences) val lockEnabled = lockEnabled(preferences)
if (lockEnabled) { if (lockEnabled) {
//Special case first lock
toolbar.navigationIcon = null
doLock() doLock()
//Check lock security //Check lock security
@ -236,6 +234,16 @@ class MainActivity : BaseActivity() {
drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) drawer.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
} }
// --> EH
//Special case and hide drawer arrow for lock controller
if(to is LockController) {
supportActionBar?.setDisplayHomeAsUpEnabled(false)
} else {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar.navigationIcon = drawerArrow
}
// <-- EH
ObjectAnimator.ofFloat(drawerArrow, "progress", if (showHamburger) 0f else 1f).start() ObjectAnimator.ofFloat(drawerArrow, "progress", if (showHamburger) 0f else 1f).start()
if (from is TabbedController) { if (from is TabbedController) {
@ -270,10 +278,9 @@ class MainActivity : BaseActivity() {
// --> EH // --> EH
//Lock code //Lock code
var willLock = false var willLock = false
var disableLock = false
override fun onRestart() { override fun onRestart() {
super.onRestart() super.onRestart()
if(willLock && lockEnabled() && !disableLock) { if(willLock && lockEnabled()) {
doLock() doLock()
} }
@ -286,6 +293,10 @@ class MainActivity : BaseActivity() {
} }
fun tryLock() { fun tryLock() {
//Do not double-lock
if(router.backstack.lastOrNull()?.controller() is LockController)
return
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val mUsageStatsManager = getSystemService("usagestats") as UsageStatsManager val mUsageStatsManager = getSystemService("usagestats") as UsageStatsManager
val time = System.currentTimeMillis() val time = System.currentTimeMillis()

View File

@ -1,9 +1,11 @@
package eu.kanade.tachiyomi.ui.setting package eu.kanade.tachiyomi.ui.setting
import android.content.Intent
import android.support.v7.preference.PreferenceScreen import android.support.v7.preference.PreferenceScreen
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import exh.ui.migration.MetadataFetchDialog import exh.ui.migration.MetadataFetchDialog
import exh.ui.login.LoginActivity import exh.ui.login.LoginController
import rx.android.schedulers.AndroidSchedulers
/** /**
* EH Settings fragment * EH Settings fragment
@ -20,9 +22,11 @@ class SettingsEhController : SettingsController() {
isPersistent = false isPersistent = false
defaultValue = false defaultValue = false
preferences.enableExhentai() preferences.enableExhentai()
.asObservable().subscribeUntilDestroy { .asObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
isChecked = it isChecked = it
} }
onChange { newVal -> onChange { newVal ->
newVal as Boolean newVal as Boolean
@ -30,7 +34,9 @@ class SettingsEhController : SettingsController() {
preferences.enableExhentai().set(false) preferences.enableExhentai().set(false)
true true
} else { } else {
startActivity(Intent(context, LoginActivity::class.java)) router.pushController(RouterTransaction.with(LoginController())
.pushChangeHandler(FadeChangeHandler())
.popChangeHandler(FadeChangeHandler()))
false false
} }
} }

View File

@ -4,6 +4,7 @@ import android.app.Dialog
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.support.v7.preference.PreferenceScreen import android.support.v7.preference.PreferenceScreen
import android.support.v7.preference.SwitchPreference
import android.view.View import android.view.View
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -13,6 +14,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.LocaleHelper import eu.kanade.tachiyomi.util.LocaleHelper
import exh.ui.lock.FingerLockPreference
import exh.ui.lock.LockPreference import exh.ui.lock.LockPreference
import kotlinx.android.synthetic.main.pref_library_columns.view.* import kotlinx.android.synthetic.main.pref_library_columns.view.*
import rx.Observable import rx.Observable
@ -176,12 +178,25 @@ class SettingsGeneralController : SettingsController() {
true true
} }
} }
LockPreference(context).apply { preferenceCategory {
key = "pref_app_lock"
title = "Application lock" title = "Application lock"
isPersistent = false
addPreference(this) LockPreference(context).apply {
key = "pref_app_lock"
isPersistent = false
addPreference(this)
}
FingerLockPreference(context).apply {
key = "pref_lock_finger"
isPersistent = false
addPreference(this)
//Call after addPreference
dependency = "pref_app_lock"
}
} }
} }

View File

@ -35,7 +35,7 @@ class BatchAddController : NucleusController<BatchAddPresenter>() {
} }
progress_dismiss_btn.clicks().subscribeUntilDestroy { progress_dismiss_btn.clicks().subscribeUntilDestroy {
presenter.currentlyAddingRelay.call(false) presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_PROGRESS_TO_INPUT)
} }
val progressSubscriptions = CompositeSubscription() val progressSubscriptions = CompositeSubscription()
@ -44,7 +44,7 @@ class BatchAddController : NucleusController<BatchAddPresenter>() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { .subscribeUntilDestroy {
progressSubscriptions.clear() progressSubscriptions.clear()
if(it) { if(it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) {
showProgress(this) showProgress(this)
progressSubscriptions += presenter.progressRelay progressSubscriptions += presenter.progressRelay
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -79,7 +79,10 @@ class BatchAddController : NucleusController<BatchAddPresenter>() {
}?.let { }?.let {
progressSubscriptions += it progressSubscriptions += it
} }
} else hideProgress(this) } else if(it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) {
hideProgress(this)
presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_IDLE)
}
} }
} }
} }

View File

@ -15,18 +15,18 @@ class BatchAddPresenter: BasePresenter<BatchAddController>() {
val progressTotalRelay = BehaviorRelay.create(0)!! val progressTotalRelay = BehaviorRelay.create(0)!!
val progressRelay = BehaviorRelay.create(0)!! val progressRelay = BehaviorRelay.create(0)!!
var eventRelay: ReplayRelay<String>? = null var eventRelay: ReplayRelay<String>? = null
val currentlyAddingRelay = BehaviorRelay.create(false)!! val currentlyAddingRelay = BehaviorRelay.create(STATE_IDLE)!!
fun addGalleries(galleries: String) { fun addGalleries(galleries: String) {
eventRelay = ReplayRelay.create() eventRelay = ReplayRelay.create()
val splitGalleries = galleries.split("\n").map { val splitGalleries = galleries.split("\n").mapNotNull {
it.trim().nullIfBlank() it.trim().nullIfBlank()
}.filterNotNull() }
progressRelay.call(0) progressRelay.call(0)
progressTotalRelay.call(splitGalleries.size) progressTotalRelay.call(splitGalleries.size)
currentlyAddingRelay.call(true) currentlyAddingRelay.call(STATE_INPUT_TO_PROGRESS)
thread { thread {
val succeeded = mutableListOf<String>() val succeeded = mutableListOf<String>()
@ -48,4 +48,10 @@ class BatchAddPresenter: BasePresenter<BatchAddController>() {
eventRelay?.call(summary) eventRelay?.call(summary)
} }
} }
companion object {
const val STATE_IDLE = 0
const val STATE_INPUT_TO_PROGRESS = 1
const val STATE_PROGRESS_TO_INPUT = 2
}
} }

View File

@ -0,0 +1,149 @@
package exh.ui.lock
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.content.Context
import android.os.Build
import android.support.v7.preference.SwitchPreferenceCompat
import android.support.v7.widget.LinearLayoutCompat
import android.util.AttributeSet
import android.view.Gravity
import android.view.ViewGroup
import android.widget.TextView
import com.afollestad.materialdialogs.MaterialDialog
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.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.setting.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.lockUseFingerprint().getOrDefault()
@SuppressLint("NewApi")
override fun onAttached() {
super.onAttached()
if(fingerprintSupported) {
updateSummary()
onChange {
if(it as Boolean)
tryChange()
else
prefs.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.Builder(context)
.title("Fingerprint verification")
.customView(linearLayout, false)
.negativeText("Cancel")
.autoDismiss(true)
.cancelable(true)
.canceledOnTouchOutside(true)
.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.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.Builder(context)
.title("Fingerprint verification failed!")
.content(result.errorMessage)
.positiveText("Ok")
.autoDismiss(true)
.cancelable(true)
.canceledOnTouchOutside(false)
.show()
dialog.dismiss()
}
}
}
dialog.setOnDismissListener {
subscription.unsubscribe()
}
}
}

View File

@ -23,8 +23,8 @@ class LockChangeHandler : AnimatorChangeHandler {
val viewAnimators = ArrayList<Animator>() val viewAnimators = ArrayList<Animator>()
if (!isPush && from != null) { if (!isPush && from != null) {
viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_X, 5f)) viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_X, 3f))
viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_Y, 5f)) viewAnimators.add(ObjectAnimator.ofFloat(from, View.SCALE_Y, 3f))
viewAnimators.add(ObjectAnimator.ofFloat(from, View.ALPHA, 0f)) viewAnimators.add(ObjectAnimator.ofFloat(from, View.ALPHA, 0f))
} }

View File

@ -1,19 +1,35 @@
package exh.ui.lock package exh.ui.lock
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.CardView
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.andrognito.pinlockview.PinLockListener import com.andrognito.pinlockview.PinLockListener
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.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.main.MainActivity
import exh.util.dpToPx
import kotlinx.android.synthetic.main.activity_lock.view.* import kotlinx.android.synthetic.main.activity_lock.view.*
import kotlinx.android.synthetic.main.main_activity.view.*
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class LockController : NucleusController<LockPresenter>() { class LockController : NucleusController<LockPresenter>() {
val prefs: PreferencesHelper by injectLazy()
override fun inflateView(inflater: LayoutInflater, container: ViewGroup) override fun inflateView(inflater: LayoutInflater, container: ViewGroup)
= inflater.inflate(R.layout.activity_lock, container, false)!! = inflater.inflate(R.layout.activity_lock, container, false)!!
@ -21,8 +37,6 @@ class LockController : NucleusController<LockPresenter>() {
override fun getTitle() = "Application locked" override fun getTitle() = "Application locked"
val prefs: PreferencesHelper by injectLazy()
override fun onViewCreated(view: View, savedViewState: Bundle?) { override fun onViewCreated(view: View, savedViewState: Bundle?) {
super.onViewCreated(view, savedViewState) super.onViewCreated(view, savedViewState)
@ -32,6 +46,7 @@ class LockController : NucleusController<LockPresenter>() {
} }
with(view) { with(view) {
//Setup pin lock
pin_lock_view.attachIndicatorDots(indicator_dots) pin_lock_view.attachIndicatorDots(indicator_dots)
pin_lock_view.pinLength = prefs.lockLength().getOrDefault() pin_lock_view.pinLength = prefs.lockLength().getOrDefault()
@ -60,6 +75,61 @@ class LockController : NucleusController<LockPresenter>() {
} }
} }
@SuppressLint("NewApi")
override fun onAttach(view: View) {
super.onAttach(view)
with(view) {
//Fingerprint
if (presenter.useFingerprint) {
swirl_container.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 typedVal = TypedValue()
activity!!.theme!!.resolveAttribute(android.R.attr.windowBackground, typedVal, true)
setBackgroundColor(typedVal.data)
//Disable elevation if dark theme is active
if (typedVal.data == resources.getColor(R.color.backgroundDark, activity!!.theme!!))
this@with.swirl_container.cardElevation = 0f
setState(SwirlView.State.OFF, false)
}
swirl_container.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.Builder(context)
.title("Fingerprint error!")
.content(it.errorMessage)
.cancelable(false)
.canceledOnTouchOutside(false)
.positiveText("Ok")
.autoDismiss(true)
.show()
icon.setState(SwirlView.State.OFF)
}
}
}
}
}
}
override fun onDetach(view: View) {
super.onDetach(view)
}
fun closeLock() { fun closeLock() {
router.popCurrentController() router.popCurrentController()
} }

View File

@ -2,11 +2,14 @@ package exh.ui.lock
import android.content.Context import android.content.Context
import android.support.v7.preference.Preference import android.support.v7.preference.Preference
import android.support.v7.preference.SwitchPreference
import android.support.v7.preference.SwitchPreferenceCompat
import android.text.InputType import android.text.InputType
import android.util.AttributeSet import android.util.AttributeSet
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.setting.onChange
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -15,7 +18,7 @@ import java.math.BigInteger
import java.security.SecureRandom import java.security.SecureRandom
class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
Preference(context, attrs) { SwitchPreferenceCompat(context, attrs) {
private val secureRandom by lazy { SecureRandom() } private val secureRandom by lazy { SecureRandom() }
@ -24,17 +27,24 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut
override fun onAttached() { override fun onAttached() {
super.onAttached() super.onAttached()
updateSummary() updateSummary()
onChange {
tryChange()
false
}
} }
private fun updateSummary() { private fun updateSummary() {
summary = if(lockEnabled(prefs)) isChecked = lockEnabled(prefs)
"Application is locked" if(isChecked) {
else title = "Lock enabled"
"Application is not locked, tap to lock" summary = "Tap to disable or change pin code"
} else {
title = "Lock disabled"
summary = "Tap to enable"
}
} }
override fun onClick() { fun tryChange() {
super.onClick()
if(!notifyLockSecurity(context)) { if(!notifyLockSecurity(context)) {
MaterialDialog.Builder(context) MaterialDialog.Builder(context)
.title("Lock application") .title("Lock application")

View File

@ -1,6 +1,19 @@
package exh.ui.lock 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 eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import uy.kohesive.injekt.injectLazy
class LockPresenter: BasePresenter<LockController>() 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.lockUseFingerprint().getOrDefault()
}

View File

@ -3,7 +3,9 @@ package exh.ui.login
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
@ -12,9 +14,9 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.EHentai
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import exh.EXH_SOURCE_ID import exh.EXH_SOURCE_ID
import kotlinx.android.synthetic.main.eh_activity_login.* import kotlinx.android.synthetic.main.eh_activity_login.view.*
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers
@ -23,70 +25,75 @@ import uy.kohesive.injekt.injectLazy
import java.net.HttpCookie import java.net.HttpCookie
/** /**
* LoginActivity * LoginController
*/ */
class LoginActivity : BaseActivity() { class LoginController : NucleusController<LoginPresenter>() {
val preferenceManager: PreferencesHelper by injectLazy() val preferenceManager: PreferencesHelper by injectLazy()
val sourceManager: SourceManager by injectLazy() val sourceManager: SourceManager by injectLazy()
override fun onCreate(savedInstanceState: Bundle?) { override fun getTitle() = "ExHentai login"
super.onCreate(savedInstanceState)
setContentView(R.layout.eh_activity_login)
setup() override fun createPresenter() = LoginPresenter()
}
fun setup() { override fun inflateView(inflater: LayoutInflater, container: ViewGroup) =
btn_cancel.setOnClickListener { onBackPressed() } inflater.inflate(R.layout.eh_activity_login, container, false)!!
btn_recheck.setOnClickListener { webview.loadUrl("http://exhentai.org/") }
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { override fun onViewCreated(view: View, savedViewState: Bundle?) {
CookieManager.getInstance().removeAllCookies { super.onViewCreated(view, savedViewState)
runOnUiThread {
startWebview() with(view) {
btn_cancel.setOnClickListener { router.popCurrentController() }
btn_recheck.setOnClickListener { webview.loadUrl("http://exhentai.org/") }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().removeAllCookies {
Observable.fromCallable {
startWebview(view)
}.subscribeOn(AndroidSchedulers.mainThread()).subscribe()
} }
} else {
CookieManager.getInstance().removeAllCookie()
startWebview(view)
} }
} else {
CookieManager.getInstance().removeAllCookie()
startWebview()
} }
} }
fun startWebview() { fun startWebview(view: View) {
webview.settings.javaScriptEnabled = true with(view) {
webview.settings.domStorageEnabled = true webview.settings.javaScriptEnabled = true
webview.settings.domStorageEnabled = true
webview.loadUrl("https://forums.e-hentai.org/index.php?act=Login") webview.loadUrl("https://forums.e-hentai.org/index.php?act=Login")
webview.setWebViewClient(object : WebViewClient() { webview.setWebViewClient(object : WebViewClient() {
override fun onPageFinished(view: WebView, url: String) { override fun onPageFinished(view: WebView, url: String) {
super.onPageFinished(view, url) super.onPageFinished(view, url)
Timber.d(url) Timber.d(url)
val parsedUrl = Uri.parse(url) val parsedUrl = Uri.parse(url)
if(parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) { if (parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
//Hide distracting content //Hide distracting content
view.loadUrl(HIDE_JS) view.loadUrl(HIDE_JS)
//Check login result //Check login result
if(parsedUrl.getQueryParameter("code")?.toInt() != 0) { if (parsedUrl.getQueryParameter("code")?.toInt() != 0) {
if(checkLoginCookies(url)) view.loadUrl("http://exhentai.org/") if (checkLoginCookies(url)) view.loadUrl("http://exhentai.org/")
} }
} else if(parsedUrl.host.equals("exhentai.org", ignoreCase = true)) { } else if (parsedUrl.host.equals("exhentai.org", ignoreCase = true)) {
//At ExHentai, check that everything worked out... //At ExHentai, check that everything worked out...
if(applyExHentaiCookies(url)) { if (applyExHentaiCookies(url)) {
preferenceManager.enableExhentai().set(true) preferenceManager.enableExhentai().set(true)
finishLogin() finishLogin(view)
}
} }
} }
} })
}) }
} }
fun finishLogin() { fun finishLogin(view: View) {
val progressDialog = MaterialDialog.Builder(this) val progressDialog = MaterialDialog.Builder(view.context)
.title("Finalizing login") .title("Finalizing login")
.progress(true, 0) .progress(true, 0)
.content("Please wait...") .content("Please wait...")
@ -108,7 +115,7 @@ class LoginActivity : BaseActivity() {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { .subscribe {
progressDialog.dismiss() progressDialog.dismiss()
onBackPressed() router.popCurrentController()
} }
} }
@ -164,14 +171,6 @@ class LoginActivity : BaseActivity() {
} }
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> onBackPressed()
else -> return super.onOptionsItemSelected(item)
}
return true
}
companion object { companion object {
const val MEMBER_ID_COOKIE = "ipb_member_id" const val MEMBER_ID_COOKIE = "ipb_member_id"
const val PASS_HASH_COOKIE = "ipb_pass_hash" const val PASS_HASH_COOKIE = "ipb_pass_hash"

View File

@ -0,0 +1,7 @@
package exh.ui.login
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
class LoginPresenter: BasePresenter<LoginController>() {
}

View File

@ -0,0 +1,8 @@
package exh.util
import android.content.Context
fun dpToPx(context: Context, dp: Int): Int {
val scale = context.resources.displayMetrics.density
return (dp * scale + 0.5f).toInt()
}

View File

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:background="@color/backgroundDark"> android:background="@color/backgroundDark">
<com.andrognito.pinlockview.PinLockView <com.andrognito.pinlockview.PinLockView
@ -26,4 +27,19 @@
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed" />
<android.support.v7.widget.CardView
android:id="@+id/swirl_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
card_view:cardCornerRadius="30dp"
card_view:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
</android.support.v7.widget.CardView>
</android.support.constraint.ConstraintLayout> </android.support.constraint.ConstraintLayout>

View File

@ -11,7 +11,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:padding="16dp "> android:padding="16dp">
<TextView <TextView
android:id="@+id/input_title_view" android:id="@+id/input_title_view"
@ -19,7 +19,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Enter the galleries to add (separated by a new line):" android:text="Enter the galleries to add (separated by a new line):"
android:textAppearance="@style/TextAppearance.Medium.Title" android:textAppearance="@style/TextAppearance.Medium.Title"
android:visibility="gone" android:visibility="visible"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -31,7 +31,7 @@
android:ems="10" android:ems="10"
android:hint="Example:\n\nhttp://e-hentai.org/g/12345/1a2b3c4e\nhttp://g.e-hentai.org/g/67890/6f7g8h9i\nhttp://exhentai.org/g/13579/1a3b5c7e\nhttps://exhentai.org/g/24680/2f4g6h8i\n" android:hint="Example:\n\nhttp://e-hentai.org/g/12345/1a2b3c4e\nhttp://g.e-hentai.org/g/67890/6f7g8h9i\nhttp://exhentai.org/g/13579/1a3b5c7e\nhttps://exhentai.org/g/24680/2f4g6h8i\n"
android:inputType="textMultiLine" android:inputType="textMultiLine"
android:visibility="gone" android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/btn_add_galleries" app:layout_constraintBottom_toTopOf="@+id/btn_add_galleries"
app:layout_constraintLeft_toLeftOf="@+id/input_title_view" app:layout_constraintLeft_toLeftOf="@+id/input_title_view"
app:layout_constraintRight_toRightOf="@+id/input_title_view" app:layout_constraintRight_toRightOf="@+id/input_title_view"
@ -42,7 +42,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Add Galleries" android:text="Add Galleries"
android:visibility="gone" android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@+id/galleries_box" app:layout_constraintLeft_toLeftOf="@+id/galleries_box"
app:layout_constraintRight_toRightOf="@+id/galleries_box" /> app:layout_constraintRight_toRightOf="@+id/galleries_box" />
@ -58,7 +58,7 @@
android:layout_marginTop="0dp" android:layout_marginTop="0dp"
android:text="Adding galleries..." android:text="Adding galleries..."
android:textAppearance="@style/TextAppearance.Medium.Title" android:textAppearance="@style/TextAppearance.Medium.Title"
android:visibility="visible" android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -71,7 +71,7 @@
android:layout_marginBottom="0dp" android:layout_marginBottom="0dp"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:visibility="visible" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/progress_dismiss_btn" app:layout_constraintBottom_toTopOf="@+id/progress_dismiss_btn"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="@+id/progress_log_wrapper" app:layout_constraintLeft_toLeftOf="@+id/progress_log_wrapper"
@ -85,7 +85,7 @@
android:singleLine="true" android:singleLine="true"
android:text="" android:text=""
android:textAlignment="textEnd" android:textAlignment="textEnd"
android:visibility="visible" android:visibility="gone"
app:layout_constraintRight_toRightOf="@+id/progress_log_wrapper" app:layout_constraintRight_toRightOf="@+id/progress_log_wrapper"
app:layout_constraintTop_toTopOf="@+id/progress_bar" /> app:layout_constraintTop_toTopOf="@+id/progress_bar" />
@ -93,15 +93,15 @@
android:id="@+id/progress_dismiss_btn" android:id="@+id/progress_dismiss_btn"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
android:layout_marginLeft="0dp" android:layout_marginLeft="0dp"
android:layout_marginRight="0dp" android:layout_marginRight="0dp"
android:text="Finish" android:text="Finish"
android:visibility="visible" android:visibility="gone"
app:layout_constraintLeft_toLeftOf="@+id/progress_log_wrapper"
app:layout_constraintRight_toRightOf="@+id/progress_log_wrapper"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintBottom_toBottomOf="@+id/btn_add_galleries" app:layout_constraintBottom_toBottomOf="@+id/btn_add_galleries"
android:layout_marginBottom="0dp" /> app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="@+id/progress_log_wrapper"
app:layout_constraintRight_toRightOf="@+id/progress_log_wrapper" />
<ScrollView <ScrollView
android:id="@+id/progress_log_wrapper" android:id="@+id/progress_log_wrapper"
@ -109,7 +109,7 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:visibility="visible" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@+id/progress_bar" app:layout_constraintBottom_toTopOf="@+id/progress_bar"
app:layout_constraintHorizontal_bias="0.0" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="@+id/progress_title_view" app:layout_constraintLeft_toLeftOf="@+id/progress_title_view"

View File

@ -24,6 +24,10 @@
<item name="android:divider">@color/dividerLight</item> <item name="android:divider">@color/dividerLight</item>
<item name="android:listDivider">@drawable/line_divider_light</item> <item name="android:listDivider">@drawable/line_divider_light</item>
<!-- Swirl (EH) -->
<item name="swirl_ridgeColor">?android:attr/textColorSecondary</item>
<item name="swirl_errorColor">?android:attr/colorAccent</item>
<!-- Themes --> <!-- Themes -->
<item name="windowActionModeOverlay">true</item> <item name="windowActionModeOverlay">true</item>
<item name="actionBarTheme">@style/Theme.ActionBar.Light</item> <item name="actionBarTheme">@style/Theme.ActionBar.Light</item>