Password protection
This commit is contained in:
@@ -69,8 +69,12 @@
|
|||||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".ui.search.global.GlobalSearchActivity"
|
<activity
|
||||||
|
android:name=".ui.search.global.GlobalSearchActivity"
|
||||||
android:label="@string/search" />
|
android:label="@string/search" />
|
||||||
|
<activity
|
||||||
|
android:name=".ui.utils.protect.ProtectActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".ui.download.DownloadService"
|
android:name=".ui.download.DownloadService"
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package org.koitharu.kotatsu.core.exceptions
|
||||||
|
|
||||||
|
class WrongPasswordException : SecurityException()
|
||||||
@@ -92,7 +92,13 @@ class AppSettings private constructor(resources: Resources, private val prefs: S
|
|||||||
setOf(TRACK_FAVOURITES, TRACK_HISTORY)
|
setOf(TRACK_FAVOURITES, TRACK_HISTORY)
|
||||||
)
|
)
|
||||||
|
|
||||||
private var sourcesOrderStr by NullableStringPreferenceDelegate(resources.getString(R.string.key_sources_order))
|
var appPassword by NullableStringPreferenceDelegate(
|
||||||
|
resources.getString(R.string.key_app_password)
|
||||||
|
)
|
||||||
|
|
||||||
|
private var sourcesOrderStr by NullableStringPreferenceDelegate(
|
||||||
|
resources.getString(R.string.key_sources_order)
|
||||||
|
)
|
||||||
|
|
||||||
var sourcesOrder: List<Int>
|
var sourcesOrder: List<Int>
|
||||||
get() = sourcesOrderStr?.split('|')?.mapNotNull(String::toIntOrNull).orEmpty()
|
get() = sourcesOrderStr?.split('|')?.mapNotNull(String::toIntOrNull).orEmpty()
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnStorageSelectListener {
|
fun interface OnStorageSelectListener {
|
||||||
|
|
||||||
fun onStorageSelected(file: File)
|
fun onStorageSelected(file: File)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,18 +61,29 @@ class TextInputDialog private constructor(private val delegate: AlertDialog) :
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setPositiveButton(@StringRes textId: Int, listener: (DialogInterface, String) -> Unit): Builder {
|
fun setPositiveButton(
|
||||||
|
@StringRes textId: Int,
|
||||||
|
listener: (DialogInterface, String) -> Unit
|
||||||
|
): Builder {
|
||||||
delegate.setPositiveButton(textId) { dialog, _ ->
|
delegate.setPositiveButton(textId) { dialog, _ ->
|
||||||
listener(dialog, view.inputEdit.text?.toString().orEmpty())
|
listener(dialog, view.inputEdit.text?.toString().orEmpty())
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNegativeButton(@StringRes textId: Int, listener: DialogInterface.OnClickListener? = null): Builder {
|
fun setNegativeButton(
|
||||||
|
@StringRes textId: Int,
|
||||||
|
listener: DialogInterface.OnClickListener? = null
|
||||||
|
): Builder {
|
||||||
delegate.setNegativeButton(textId, listener)
|
delegate.setNegativeButton(textId, listener)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setOnCancelListener(listener: DialogInterface.OnCancelListener): Builder {
|
||||||
|
delegate.setOnCancelListener(listener)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun create() =
|
fun create() =
|
||||||
TextInputDialog(delegate.create())
|
TextInputDialog(delegate.create())
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class CheckableImageView @JvmOverloads constructor(
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnCheckedChangeListener {
|
fun interface OnCheckedChangeListener {
|
||||||
|
|
||||||
fun onCheckedChanged(view: CheckableImageView, isChecked: Boolean)
|
fun onCheckedChanged(view: CheckableImageView, isChecked: Boolean)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import org.koitharu.kotatsu.ui.search.SearchHelper
|
|||||||
import org.koitharu.kotatsu.ui.settings.AppUpdateChecker
|
import org.koitharu.kotatsu.ui.settings.AppUpdateChecker
|
||||||
import org.koitharu.kotatsu.ui.settings.SettingsActivity
|
import org.koitharu.kotatsu.ui.settings.SettingsActivity
|
||||||
import org.koitharu.kotatsu.ui.tracker.TrackWorker
|
import org.koitharu.kotatsu.ui.tracker.TrackWorker
|
||||||
|
import org.koitharu.kotatsu.ui.utils.protect.AppProtectHelper
|
||||||
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.utils.ext.resolveDp
|
import org.koitharu.kotatsu.utils.ext.resolveDp
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
@@ -71,6 +72,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
} ?: run {
|
} ?: run {
|
||||||
openDefaultSection()
|
openDefaultSection()
|
||||||
}
|
}
|
||||||
|
if (AppProtectHelper.check(this)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
TrackWorker.setup(applicationContext)
|
TrackWorker.setup(applicationContext)
|
||||||
AppUpdateChecker(this).invoke()
|
AppUpdateChecker(this).invoke()
|
||||||
}
|
}
|
||||||
@@ -78,6 +82,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
closeable?.close()
|
closeable?.close()
|
||||||
settings.unsubscribe(this)
|
settings.unsubscribe(this)
|
||||||
|
AppProtectHelper.lock()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.ui.list.filter
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaFilter
|
import org.koitharu.kotatsu.core.model.MangaFilter
|
||||||
|
|
||||||
interface OnFilterChangedListener {
|
fun interface OnFilterChangedListener {
|
||||||
|
|
||||||
fun onFilterChanged(filter: MangaFilter)
|
fun onFilterChanged(filter: MangaFilter)
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnChapterChangeListener {
|
fun interface OnChapterChangeListener {
|
||||||
|
|
||||||
fun onChapterChanged(chapter: MangaChapter)
|
fun onChapterChanged(chapter: MangaChapter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.ui.reader.thumbnails
|
|||||||
|
|
||||||
import org.koitharu.kotatsu.core.model.MangaPage
|
import org.koitharu.kotatsu.core.model.MangaPage
|
||||||
|
|
||||||
interface OnPageSelectListener {
|
fun interface OnPageSelectListener {
|
||||||
|
|
||||||
fun onPageSelected(page: MangaPage)
|
fun onPageSelected(page: MangaPage)
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
package org.koitharu.kotatsu.ui.settings
|
package org.koitharu.kotatsu.ui.settings
|
||||||
|
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.text.InputType
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.collection.arrayMapOf
|
import androidx.collection.arrayMapOf
|
||||||
import androidx.preference.MultiSelectListPreference
|
import androidx.preference.*
|
||||||
import androidx.preference.Preference
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import androidx.preference.SeekBarPreference
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.model.MangaSource
|
import org.koitharu.kotatsu.core.model.MangaSource
|
||||||
import org.koitharu.kotatsu.core.prefs.ListMode
|
import org.koitharu.kotatsu.core.prefs.ListMode
|
||||||
import org.koitharu.kotatsu.ui.common.BasePreferenceFragment
|
import org.koitharu.kotatsu.ui.common.BasePreferenceFragment
|
||||||
import org.koitharu.kotatsu.ui.common.dialog.StorageSelectDialog
|
import org.koitharu.kotatsu.ui.common.dialog.StorageSelectDialog
|
||||||
|
import org.koitharu.kotatsu.ui.common.dialog.TextInputDialog
|
||||||
import org.koitharu.kotatsu.ui.list.ListModeSelectDialog
|
import org.koitharu.kotatsu.ui.list.ListModeSelectDialog
|
||||||
import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider
|
import org.koitharu.kotatsu.ui.settings.utils.MultiSummaryProvider
|
||||||
import org.koitharu.kotatsu.ui.tracker.TrackWorker
|
import org.koitharu.kotatsu.ui.tracker.TrackWorker
|
||||||
import org.koitharu.kotatsu.utils.ext.getStorageName
|
import org.koitharu.kotatsu.utils.ext.getStorageName
|
||||||
|
import org.koitharu.kotatsu.utils.ext.md5
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
@@ -50,6 +52,8 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
|||||||
summary = settings.getStorageDir(context)?.getStorageName(context)
|
summary = settings.getStorageDir(context)?.getStorageName(context)
|
||||||
?: getString(R.string.not_available)
|
?: getString(R.string.not_available)
|
||||||
}
|
}
|
||||||
|
findPreference<SwitchPreference>(R.string.key_protect_app)?.isChecked =
|
||||||
|
!settings.appPassword.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||||
@@ -114,6 +118,14 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
|||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
getString(R.string.key_protect_app) -> {
|
||||||
|
if ((preference as? SwitchPreference ?: return false).isChecked) {
|
||||||
|
enableAppProtection(preference)
|
||||||
|
} else {
|
||||||
|
settings.appPassword = null
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> super.onPreferenceTreeClick(preference)
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +134,56 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
|
|||||||
settings.setStorageDir(context ?: return, file)
|
settings.setStorageDir(context ?: return, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun enableAppProtection(preference: SwitchPreference) {
|
||||||
|
val ctx = preference.context ?: return
|
||||||
|
val cancelListener =
|
||||||
|
object : DialogInterface.OnCancelListener, DialogInterface.OnClickListener {
|
||||||
|
|
||||||
|
override fun onCancel(dialog: DialogInterface?) {
|
||||||
|
settings.appPassword = null
|
||||||
|
preference.isChecked = false
|
||||||
|
preference.isEnabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(dialog: DialogInterface?, which: Int) = onCancel(dialog)
|
||||||
|
}
|
||||||
|
preference.isEnabled = false
|
||||||
|
TextInputDialog.Builder(ctx)
|
||||||
|
.setInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
|
||||||
|
.setHint(R.string.enter_password)
|
||||||
|
.setNegativeButton(android.R.string.cancel, cancelListener)
|
||||||
|
.setOnCancelListener(cancelListener)
|
||||||
|
.setPositiveButton(android.R.string.ok) { d, password ->
|
||||||
|
if (password.isBlank()) {
|
||||||
|
cancelListener.onCancel(d)
|
||||||
|
return@setPositiveButton
|
||||||
|
}
|
||||||
|
TextInputDialog.Builder(ctx)
|
||||||
|
.setInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD)
|
||||||
|
.setHint(R.string.repeat_password)
|
||||||
|
.setNegativeButton(android.R.string.cancel, cancelListener)
|
||||||
|
.setOnCancelListener(cancelListener)
|
||||||
|
.setPositiveButton(android.R.string.ok) { d2, password2 ->
|
||||||
|
if (password == password2) {
|
||||||
|
settings.appPassword = password.md5()
|
||||||
|
preference.isChecked = true
|
||||||
|
preference.isEnabled = true
|
||||||
|
} else {
|
||||||
|
cancelListener.onCancel(d2)
|
||||||
|
Snackbar.make(
|
||||||
|
listView,
|
||||||
|
R.string.passwords_mismatch,
|
||||||
|
Snackbar.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}.setTitle(preference.title)
|
||||||
|
.create()
|
||||||
|
.show()
|
||||||
|
}.setTitle(preference.title)
|
||||||
|
.create()
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
||||||
val LIST_MODES = arrayMapOf(
|
val LIST_MODES = arrayMapOf(
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.BuildConfig
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
|
||||||
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
|
||||||
|
import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
|
|
||||||
inline fun <T, R> T.safe(action: T.() -> R?) = try {
|
inline fun <T, R> T.safe(action: T.() -> R?) = try {
|
||||||
@@ -39,6 +40,7 @@ fun Throwable.getDisplayMessage(resources: Resources) = when (this) {
|
|||||||
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
is UnsupportedFileException -> resources.getString(R.string.text_file_not_supported)
|
||||||
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
is EmptyHistoryException -> resources.getString(R.string.history_is_empty)
|
||||||
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
is SocketTimeoutException -> resources.getString(R.string.network_error)
|
||||||
|
is WrongPasswordException -> resources.getString(R.string.wrong_password)
|
||||||
else -> message ?: resources.getString(R.string.error_occurred)
|
else -> message ?: resources.getString(R.string.error_occurred)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package org.koitharu.kotatsu.utils.ext
|
package org.koitharu.kotatsu.utils.ext
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import java.math.BigInteger
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
import java.security.MessageDigest
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun String.longHashCode(): Long {
|
fun String.longHashCode(): Long {
|
||||||
@@ -100,4 +102,11 @@ fun ByteArray.byte2HexFormatted(): String? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return str.toString()
|
return str.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.md5(): String {
|
||||||
|
val md = MessageDigest.getInstance("MD5")
|
||||||
|
return BigInteger(1, md.digest(toByteArray()))
|
||||||
|
.toString(16)
|
||||||
|
.padStart(32, '0')
|
||||||
}
|
}
|
||||||
47
app/src/main/res/layout/activity_protect.xml
Normal file
47
app/src/main/res/layout/activity_protect.xml
Normal file
@@ -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>
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:imeOptions="actionDone"
|
android:imeOptions="actionDone"
|
||||||
android:maxLines="1"
|
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
tools:text="@tools:sample/lorem[2]" />
|
tools:text="@tools:sample/lorem[2]" />
|
||||||
|
|
||||||
|
|||||||
12
app/src/main/res/menu/opt_protect.xml
Normal file
12
app/src/main/res/menu/opt_protect.xml
Normal file
@@ -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>
|
||||||
@@ -147,4 +147,10 @@
|
|||||||
<string name="feed_will_update_soon">Обновление скоро начнётся</string>
|
<string name="feed_will_update_soon">Обновление скоро начнётся</string>
|
||||||
<string name="track_sources">Проверять обновления манги</string>
|
<string name="track_sources">Проверять обновления манги</string>
|
||||||
<string name="dont_check">Не проверять</string>
|
<string name="dont_check">Не проверять</string>
|
||||||
|
<string name="enter_password">Введите пароль</string>
|
||||||
|
<string name="wrong_password">Неверный пароль</string>
|
||||||
|
<string name="protect_application">Защитить приложение</string>
|
||||||
|
<string name="protect_application_summary">Запрашивать пароль при запуске приложения</string>
|
||||||
|
<string name="repeat_password">Повторите пароль</string>
|
||||||
|
<string name="passwords_mismatch">Пароли не совпадают</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -23,6 +23,8 @@
|
|||||||
<string name="key_notifications_vibrate">notifications_vibrate</string>
|
<string name="key_notifications_vibrate">notifications_vibrate</string>
|
||||||
<string name="key_notifications_light">notifications_light</string>
|
<string name="key_notifications_light">notifications_light</string>
|
||||||
<string name="key_reader_animation">reader_animation</string>
|
<string name="key_reader_animation">reader_animation</string>
|
||||||
|
<string name="key_app_password">app_password</string>
|
||||||
|
<string name="key_protect_app">protect_app</string>
|
||||||
|
|
||||||
<string name="key_parser_domain">domain</string>
|
<string name="key_parser_domain">domain</string>
|
||||||
<string name="key_parser_ssl">ssl</string>
|
<string name="key_parser_ssl">ssl</string>
|
||||||
|
|||||||
@@ -148,4 +148,10 @@
|
|||||||
<string name="feed_will_update_soon">Feed update will start soon</string>
|
<string name="feed_will_update_soon">Feed update will start soon</string>
|
||||||
<string name="track_sources">Check updates for manga</string>
|
<string name="track_sources">Check updates for manga</string>
|
||||||
<string name="dont_check">Don`t check</string>
|
<string name="dont_check">Don`t check</string>
|
||||||
|
<string name="enter_password">Enter password</string>
|
||||||
|
<string name="wrong_password">Wrong password</string>
|
||||||
|
<string name="protect_application">Protect application</string>
|
||||||
|
<string name="protect_application_summary">Ask for password on application start</string>
|
||||||
|
<string name="repeat_password">Repeat password</string>
|
||||||
|
<string name="passwords_mismatch">Passwords do not match</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -48,6 +48,13 @@
|
|||||||
android:title="@string/history_and_cache"
|
android:title="@string/history_and_cache"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:key="@string/key_protect_app"
|
||||||
|
android:persistent="false"
|
||||||
|
android:summary="@string/protect_application_summary"
|
||||||
|
android:title="@string/protect_application"
|
||||||
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<MultiSelectListPreference
|
<MultiSelectListPreference
|
||||||
android:defaultValue="@array/values_reader_switchers_default"
|
android:defaultValue="@array/values_reader_switchers_default"
|
||||||
android:entries="@array/reader_switchers"
|
android:entries="@array/reader_switchers"
|
||||||
@@ -57,7 +64,7 @@
|
|||||||
app:allowDividerAbove="true"
|
app:allowDividerAbove="true"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="@string/key_reader_animation"
|
android:key="@string/key_reader_animation"
|
||||||
android:title="@string/pages_animation"
|
android:title="@string/pages_animation"
|
||||||
|
|||||||
Reference in New Issue
Block a user