Password protection
parent
55fc1aeadd
commit
e9bce8ef15
@ -0,0 +1,3 @@
|
|||||||
|
package org.koitharu.kotatsu.core.exceptions
|
||||||
|
|
||||||
|
class WrongPasswordException : SecurityException()
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package org.koitharu.kotatsu.domain.history
|
package org.koitharu.kotatsu.domain.history
|
||||||
|
|
||||||
interface OnHistoryChangeListener {
|
fun interface OnHistoryChangeListener {
|
||||||
|
|
||||||
fun onHistoryChanged()
|
fun onHistoryChanged()
|
||||||
}
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.utils.protect
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.ui.list.MainActivity
|
||||||
|
|
||||||
|
object AppProtectHelper : KoinComponent {
|
||||||
|
|
||||||
|
val settings by inject<AppSettings>()
|
||||||
|
private var isUnlocked = settings.appPassword.isNullOrEmpty()
|
||||||
|
|
||||||
|
fun unlock(activity: Activity) {
|
||||||
|
isUnlocked = true
|
||||||
|
with(activity) {
|
||||||
|
startActivity(Intent(this, MainActivity::class.java))
|
||||||
|
finishAfterTransition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lock() {
|
||||||
|
isUnlocked = settings.appPassword.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun check(activity: Activity): Boolean {
|
||||||
|
return if (!isUnlocked) {
|
||||||
|
with(activity) {
|
||||||
|
startActivity(ProtectActivity.newIntent(this))
|
||||||
|
finishAfterTransition()
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.utils.protect
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.TextView
|
||||||
|
import kotlinx.android.synthetic.main.activity_protect.*
|
||||||
|
import moxy.ktx.moxyPresenter
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.ui.common.BaseActivity
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
|
|
||||||
|
class ProtectActivity : BaseActivity(), ProtectView, TextView.OnEditorActionListener, TextWatcher {
|
||||||
|
|
||||||
|
private val presenter by moxyPresenter(factory = ::ProtectPresenter)
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_protect)
|
||||||
|
edit_password.setOnEditorActionListener(this)
|
||||||
|
edit_password.addTextChangedListener(this)
|
||||||
|
supportActionBar?.run {
|
||||||
|
setDisplayHomeAsUpEnabled(true)
|
||||||
|
setHomeAsUpIndicator(R.drawable.ic_cross)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.opt_protect, menu)
|
||||||
|
return super.onCreateOptionsMenu(menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||||
|
R.id.action_done -> {
|
||||||
|
presenter.tryUnlock(edit_password.text?.toString().orEmpty())
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
||||||
|
return if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
|
presenter.tryUnlock(edit_password.text?.toString().orEmpty())
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
layout_password.error = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUnlockSuccess() {
|
||||||
|
AppProtectHelper.unlock(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(e: Throwable) {
|
||||||
|
layout_password.error = e.getDisplayMessage(resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadingStateChanged(isLoading: Boolean) {
|
||||||
|
layout_password.isEnabled = !isLoading
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newIntent(context: Context) = Intent(context, ProtectActivity::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.utils.protect
|
||||||
|
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.ui.common.BasePresenter
|
||||||
|
import org.koitharu.kotatsu.utils.ext.md5
|
||||||
|
|
||||||
|
class ProtectPresenter : BasePresenter<ProtectView>() {
|
||||||
|
|
||||||
|
private val settings by inject<AppSettings>()
|
||||||
|
|
||||||
|
fun tryUnlock(password: String) {
|
||||||
|
launchLoadingJob {
|
||||||
|
val passwordHash = password.md5()
|
||||||
|
val appPasswordHash = settings.appPassword
|
||||||
|
if (passwordHash == appPasswordHash) {
|
||||||
|
viewState.onUnlockSuccess()
|
||||||
|
} else {
|
||||||
|
delay(PASSWORD_COMPARE_DELAY)
|
||||||
|
throw WrongPasswordException()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
const val PASSWORD_COMPARE_DELAY = 1_000L
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package org.koitharu.kotatsu.ui.utils.protect
|
||||||
|
|
||||||
|
import moxy.viewstate.strategy.alias.SingleState
|
||||||
|
import org.koitharu.kotatsu.ui.common.BaseMvpView
|
||||||
|
|
||||||
|
interface ProtectView : BaseMvpView {
|
||||||
|
|
||||||
|
@SingleState
|
||||||
|
fun onUnlockSuccess()
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/appbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?colorPrimary"
|
||||||
|
android:theme="@style/AppToolbarTheme">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_scrollFlags="scroll|enterAlways"
|
||||||
|
app:popupTheme="@style/AppPopupTheme" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/layout_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="20dp"
|
||||||
|
app:boxBackgroundColor="@android:color/transparent"
|
||||||
|
app:boxBackgroundMode="filled"
|
||||||
|
app:endIconMode="password_toggle"
|
||||||
|
app:errorEnabled="true"
|
||||||
|
app:errorTextColor="@color/error">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/edit_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/enter_password"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_done"
|
||||||
|
android:icon="@drawable/ic_done"
|
||||||
|
android:orderInCategory="0"
|
||||||
|
android:title="@string/done"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
</menu>
|
||||||
Loading…
Reference in New Issue