diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e3caab9cc..1f394d13e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -69,8 +69,12 @@
-
+
get() = sourcesOrderStr?.split('|')?.mapNotNull(String::toIntOrNull).orEmpty()
diff --git a/app/src/main/java/org/koitharu/kotatsu/domain/history/OnHistoryChangeListener.kt b/app/src/main/java/org/koitharu/kotatsu/domain/history/OnHistoryChangeListener.kt
index 8058cd8ac..ee70791a9 100644
--- a/app/src/main/java/org/koitharu/kotatsu/domain/history/OnHistoryChangeListener.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/domain/history/OnHistoryChangeListener.kt
@@ -1,6 +1,6 @@
package org.koitharu.kotatsu.domain.history
-interface OnHistoryChangeListener {
+fun interface OnHistoryChangeListener {
fun onHistoryChanged()
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/StorageSelectDialog.kt b/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/StorageSelectDialog.kt
index e1799ce86..e718bb27c 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/StorageSelectDialog.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/StorageSelectDialog.kt
@@ -78,7 +78,7 @@ class StorageSelectDialog private constructor(private val delegate: AlertDialog)
}
- interface OnStorageSelectListener {
+ fun interface OnStorageSelectListener {
fun onStorageSelected(file: File)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/TextInputDialog.kt b/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/TextInputDialog.kt
index 1ce712294..d4e4f3d8a 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/TextInputDialog.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/common/dialog/TextInputDialog.kt
@@ -61,18 +61,29 @@ class TextInputDialog private constructor(private val delegate: AlertDialog) :
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, _ ->
listener(dialog, view.inputEdit.text?.toString().orEmpty())
}
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)
return this
}
+ fun setOnCancelListener(listener: DialogInterface.OnCancelListener): Builder {
+ delegate.setOnCancelListener(listener)
+ return this
+ }
+
fun create() =
TextInputDialog(delegate.create())
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/common/widgets/CheckableImageView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/common/widgets/CheckableImageView.kt
index f1a2e46a5..7513e3605 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/common/widgets/CheckableImageView.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/common/widgets/CheckableImageView.kt
@@ -54,7 +54,7 @@ class CheckableImageView @JvmOverloads constructor(
return state
}
- interface OnCheckedChangeListener {
+ fun interface OnCheckedChangeListener {
fun onCheckedChanged(view: CheckableImageView, isChecked: Boolean)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt
index 4cc046bfe..12673ccb1 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/MainActivity.kt
@@ -35,6 +35,7 @@ import org.koitharu.kotatsu.ui.search.SearchHelper
import org.koitharu.kotatsu.ui.settings.AppUpdateChecker
import org.koitharu.kotatsu.ui.settings.SettingsActivity
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.resolveDp
import java.io.Closeable
@@ -71,6 +72,9 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
} ?: run {
openDefaultSection()
}
+ if (AppProtectHelper.check(this)) {
+ return
+ }
TrackWorker.setup(applicationContext)
AppUpdateChecker(this).invoke()
}
@@ -78,6 +82,7 @@ class MainActivity : BaseActivity(), NavigationView.OnNavigationItemSelectedList
override fun onDestroy() {
closeable?.close()
settings.unsubscribe(this)
+ AppProtectHelper.lock()
super.onDestroy()
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/OnFilterChangedListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/OnFilterChangedListener.kt
index a52916b5b..8a2d93978 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/OnFilterChangedListener.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/list/filter/OnFilterChangedListener.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.ui.list.filter
import org.koitharu.kotatsu.core.model.MangaFilter
-interface OnFilterChangedListener {
+fun interface OnFilterChangedListener {
fun onFilterChanged(filter: MangaFilter)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ChaptersDialog.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ChaptersDialog.kt
index 9239cd2d6..9e10d7f68 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/ChaptersDialog.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/ChaptersDialog.kt
@@ -55,7 +55,7 @@ class ChaptersDialog : AlertDialogFragment(R.layout.dialog_chapters),
}
}
- interface OnChapterChangeListener {
+ fun interface OnChapterChangeListener {
fun onChapterChanged(chapter: MangaChapter)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt
index 5a2255e45..f99bf76bf 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/reader/thumbnails/OnPageSelectListener.kt
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.ui.reader.thumbnails
import org.koitharu.kotatsu.core.model.MangaPage
-interface OnPageSelectListener {
+fun interface OnPageSelectListener {
- fun onPageSelected(page: MangaPage)
+ fun onPageSelected(page: MangaPage)
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt
index 8b81724cc..d4c8274fc 100644
--- a/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/settings/MainSettingsFragment.kt
@@ -1,26 +1,28 @@
package org.koitharu.kotatsu.ui.settings
+import android.content.DialogInterface
import android.content.Intent
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import android.provider.Settings
+import android.text.InputType
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.arrayMapOf
-import androidx.preference.MultiSelectListPreference
-import androidx.preference.Preference
-import androidx.preference.PreferenceScreen
-import androidx.preference.SeekBarPreference
+import androidx.preference.*
+import com.google.android.material.snackbar.Snackbar
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.ui.common.BasePreferenceFragment
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.settings.utils.MultiSummaryProvider
import org.koitharu.kotatsu.ui.tracker.TrackWorker
import org.koitharu.kotatsu.utils.ext.getStorageName
+import org.koitharu.kotatsu.utils.ext.md5
import java.io.File
@@ -50,6 +52,8 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
summary = settings.getStorageDir(context)?.getStorageName(context)
?: getString(R.string.not_available)
}
+ findPreference(R.string.key_protect_app)?.isChecked =
+ !settings.appPassword.isNullOrEmpty()
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
@@ -114,6 +118,14 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
.show()
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)
}
}
@@ -122,6 +134,56 @@ class MainSettingsFragment : BasePreferenceFragment(R.string.settings),
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 {
val LIST_MODES = arrayMapOf(
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/AppProtectHelper.kt b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/AppProtectHelper.kt
new file mode 100644
index 000000000..9b92cacb4
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/AppProtectHelper.kt
@@ -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()
+ 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
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectActivity.kt b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectActivity.kt
new file mode 100644
index 000000000..de6621bd8
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectActivity.kt
@@ -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)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectPresenter.kt b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectPresenter.kt
new file mode 100644
index 000000000..aab2ecf29
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectPresenter.kt
@@ -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() {
+
+ private val settings by inject()
+
+ 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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectView.kt b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectView.kt
new file mode 100644
index 000000000..f2c7e4188
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectView.kt
@@ -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()
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt
index 9e8ae8a2a..1a6fdba10 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/CommonExt.kt
@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.EmptyHistoryException
import org.koitharu.kotatsu.core.exceptions.UnsupportedFileException
+import org.koitharu.kotatsu.core.exceptions.WrongPasswordException
import java.net.SocketTimeoutException
inline fun 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 EmptyHistoryException -> resources.getString(R.string.history_is_empty)
is SocketTimeoutException -> resources.getString(R.string.network_error)
+ is WrongPasswordException -> resources.getString(R.string.wrong_password)
else -> message ?: resources.getString(R.string.error_occurred)
}
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt b/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt
index 871f3ca38..698db4530 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/ext/StringExt.kt
@@ -1,7 +1,9 @@
package org.koitharu.kotatsu.utils.ext
import android.net.Uri
+import java.math.BigInteger
import java.net.URLEncoder
+import java.security.MessageDigest
import java.util.*
fun String.longHashCode(): Long {
@@ -100,4 +102,11 @@ fun ByteArray.byte2HexFormatted(): String? {
}
}
return str.toString()
+}
+
+fun String.md5(): String {
+ val md = MessageDigest.getInstance("MD5")
+ return BigInteger(1, md.digest(toByteArray()))
+ .toString(16)
+ .padStart(32, '0')
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_protect.xml b/app/src/main/res/layout/activity_protect.xml
new file mode 100644
index 000000000..a218369c1
--- /dev/null
+++ b/app/src/main/res/layout/activity_protect.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_input.xml b/app/src/main/res/layout/dialog_input.xml
index e7958fc66..9d7841959 100644
--- a/app/src/main/res/layout/dialog_input.xml
+++ b/app/src/main/res/layout/dialog_input.xml
@@ -21,7 +21,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
- android:maxLines="1"
android:singleLine="true"
tools:text="@tools:sample/lorem[2]" />
diff --git a/app/src/main/res/menu/opt_protect.xml b/app/src/main/res/menu/opt_protect.xml
new file mode 100644
index 000000000..425f8e7dc
--- /dev/null
+++ b/app/src/main/res/menu/opt_protect.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index dac5db2da..519c804af 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -147,4 +147,10 @@
Обновление скоро начнётся
Проверять обновления манги
Не проверять
+ Введите пароль
+ Неверный пароль
+ Защитить приложение
+ Запрашивать пароль при запуске приложения
+ Повторите пароль
+ Пароли не совпадают
\ No newline at end of file
diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml
index 56ea250de..049e10328 100644
--- a/app/src/main/res/values/constants.xml
+++ b/app/src/main/res/values/constants.xml
@@ -23,6 +23,8 @@
notifications_vibrate
notifications_light
reader_animation
+ app_password
+ protect_app
domain
ssl
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ceb9024dd..eb16bb209 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -148,4 +148,10 @@
Feed update will start soon
Check updates for manga
Don`t check
+ Enter password
+ Wrong password
+ Protect application
+ Ask for password on application start
+ Repeat password
+ Passwords do not match
\ No newline at end of file
diff --git a/app/src/main/res/xml/pref_main.xml b/app/src/main/res/xml/pref_main.xml
index 1cada76e6..e1f84f00a 100644
--- a/app/src/main/res/xml/pref_main.xml
+++ b/app/src/main/res/xml/pref_main.xml
@@ -48,6 +48,13 @@
android:title="@string/history_and_cache"
app:iconSpaceReserved="false" />
+
+
-