From e9bce8ef1539ae07c2f7f1d5086c68903d7544a6 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 11 Oct 2020 16:45:29 +0300 Subject: [PATCH] Password protection --- app/src/main/AndroidManifest.xml | 6 +- .../core/exceptions/WrongPasswordException.kt | 3 + .../kotatsu/core/prefs/AppSettings.kt | 8 +- .../domain/history/OnHistoryChangeListener.kt | 2 +- .../ui/common/dialog/StorageSelectDialog.kt | 2 +- .../ui/common/dialog/TextInputDialog.kt | 15 +++- .../ui/common/widgets/CheckableImageView.kt | 2 +- .../koitharu/kotatsu/ui/list/MainActivity.kt | 5 ++ .../ui/list/filter/OnFilterChangedListener.kt | 2 +- .../kotatsu/ui/reader/ChaptersDialog.kt | 2 +- .../reader/thumbnails/OnPageSelectListener.kt | 4 +- .../ui/settings/MainSettingsFragment.kt | 70 +++++++++++++++- .../ui/utils/protect/AppProtectHelper.kt | 38 +++++++++ .../ui/utils/protect/ProtectActivity.kt | 80 +++++++++++++++++++ .../ui/utils/protect/ProtectPresenter.kt | 31 +++++++ .../kotatsu/ui/utils/protect/ProtectView.kt | 10 +++ .../koitharu/kotatsu/utils/ext/CommonExt.kt | 2 + .../koitharu/kotatsu/utils/ext/StringExt.kt | 9 +++ app/src/main/res/layout/activity_protect.xml | 47 +++++++++++ app/src/main/res/layout/dialog_input.xml | 1 - app/src/main/res/menu/opt_protect.xml | 12 +++ app/src/main/res/values-ru/strings.xml | 6 ++ app/src/main/res/values/constants.xml | 2 + app/src/main/res/values/strings.xml | 6 ++ app/src/main/res/xml/pref_main.xml | 9 ++- 25 files changed, 357 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/org/koitharu/kotatsu/core/exceptions/WrongPasswordException.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/AppProtectHelper.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectActivity.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectPresenter.kt create mode 100644 app/src/main/java/org/koitharu/kotatsu/ui/utils/protect/ProtectView.kt create mode 100644 app/src/main/res/layout/activity_protect.xml create mode 100644 app/src/main/res/menu/opt_protect.xml 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" /> + + -