From 243bffebf989f8de72f7aaebc1bf5a1073e1a08c Mon Sep 17 00:00:00 2001 From: Jay Date: Sat, 22 Feb 2020 17:05:39 -0800 Subject: [PATCH] Cleanup and moving biometrics stuff + added secure sceen option Search activity no longer asks for biometrics, but will ask for it to perform certain tasks Co-Authored-By: arkon --- app/src/main/AndroidManifest.xml | 2 +- app/src/main/java/eu/kanade/tachiyomi/App.kt | 4 +- .../data/preference/PreferenceKeys.kt | 2 + .../data/preference/PreferencesHelper.kt | 2 + .../ui/base/activity/BaseActivity.kt | 9 +++ .../ui/base/activity/BaseRxActivity.kt | 12 ++++ .../kanade/tachiyomi/ui/main/MainActivity.kt | 20 +----- .../tachiyomi/ui/main/SearchActivity.kt | 3 +- .../tachiyomi/ui/manga/MangaController.kt | 19 ++++- .../ui/manga/chapter/ChapterHolder.kt | 20 ++++-- .../tachiyomi/ui/manga/chapter/ChapterItem.kt | 1 + .../ui/manga/chapter/ChaptersAdapter.kt | 16 +++-- .../ui/manga/chapter/ChaptersController.kt | 31 ++++++++- .../ui/manga/chapter/ChaptersPresenter.kt | 20 +++++- .../ui/manga/info/MangaInfoController.kt | 39 +++++++---- .../ui/manga/track/TrackController.kt | 35 ++++++++-- .../tachiyomi/ui/reader/ReaderActivity.kt | 24 +------ .../{main => security}/BiometricActivity.kt | 14 ++-- .../ui/security/SecureActivityDelegate.kt | 57 +++++++++++++++ .../ui/setting/SettingsGeneralController.kt | 69 +++++++++++-------- .../ui/setting/SettingsLibraryController.kt | 3 +- .../widget/preference/IntListMatPreference.kt | 3 +- .../widget/preference/ListMatPreference.kt | 7 +- .../widget/preference/MatPreference.kt | 15 ++-- .../preference/MultiListMatPreference.kt | 9 ++- .../main/res/drawable/ic_lock_white_24dp.xml | 5 ++ app/src/main/res/layout/track_controller.xml | 21 +++++- app/src/main/res/values/strings.xml | 4 ++ 28 files changed, 324 insertions(+), 142 deletions(-) rename app/src/main/java/eu/kanade/tachiyomi/ui/{main => security}/BiometricActivity.kt (73%) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt create mode 100644 app/src/main/res/drawable/ic_lock_white_24dp.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5f2ca60aae..f4cf12020a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -62,7 +62,7 @@ android:name=".ui.webview.WebViewActivity" android:configChanges="uiMode|orientation|screenSize"/> + android:name=".ui.security.BiometricActivity" /> = 0) { - MainActivity.unlocked = false + SecureActivityDelegate.locked = true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt index d735bab70c..ca7ddad342 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt @@ -131,6 +131,8 @@ object PreferenceKeys { const val lastUnlock = "last_unlock" + const val secureScreen = "secure_screen" + const val removeArticles = "remove_articles" const val skipPreMigration = "skip_pre_migration" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 008b7e0905..6e595872b9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -215,6 +215,8 @@ class PreferencesHelper(val context: Context) { fun lastUnlock() = rxPrefs.getLong(Keys.lastUnlock, 0) + fun secureScreen() = rxPrefs.getBoolean(Keys.secureScreen, false) + fun removeArticles() = rxPrefs.getBoolean(Keys.removeArticles, false) fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt index 7ce33c448a..d28dd773a3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseActivity.kt @@ -5,6 +5,9 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.ui.main.SearchActivity +import eu.kanade.tachiyomi.ui.security.BiometricActivity +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.LocaleHelper import uy.kohesive.injekt.injectLazy @@ -32,6 +35,12 @@ abstract class BaseActivity : AppCompatActivity() { else -> R.style.Theme_Tachiyomi }) super.onCreate(savedInstanceState) + SecureActivityDelegate.setSecure(this) } + override fun onResume() { + super.onResume() + if (this !is BiometricActivity && this !is SearchActivity) + SecureActivityDelegate.promptLockIfNeeded(this) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt index 7d120f5cb1..4bfe865053 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseRxActivity.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.ui.base.activity +import android.os.Bundle import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.LocaleHelper import nucleus.view.NucleusAppCompatActivity @@ -11,4 +13,14 @@ abstract class BaseRxActivity

> : NucleusAppCompatActivity

= - preferences.lastUnlock().getOrDefault() + 60 * 1000 * preferences.lockAfter().getOrDefault())) { - val intent = Intent(this, BiometricActivity::class.java) - startActivity(intent) - this.overridePendingTransition(0, 0) - } - } - else if (useBiometrics) - preferences.useBiometrics().set(false) } private fun getExtensionUpdates() { @@ -480,7 +468,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { val baseController = router.backstack.last().controller() as? BaseController if (if (router.backstackSize == 1) !(baseController?.handleRootBack() ?: false) else !router.handleBack()) { - unlocked = false + SecureActivityDelegate.locked = true super.onBackPressed() } } @@ -593,8 +581,6 @@ open class MainActivity : BaseActivity(), DownloadServiceListener { private const val URL_HELP = "https://tachiyomi.org/help/" - var unlocked = false - var usingBottomNav = true internal set } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt index b06364c40b..b8aeeaeb95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/SearchActivity.kt @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.ui.base.controller.NoToolbarElevationController import eu.kanade.tachiyomi.ui.base.controller.TabbedController import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updatePadding @@ -148,7 +149,7 @@ class SearchActivity: MainActivity() { override fun onBackPressed() { if (router.backstack.size <= 1 || !router.handleBack()) { - unlocked = false + SecureActivityDelegate.locked = true super.onBackPressed() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 766128365f..a8d97343d2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.ui.manga import android.Manifest.permission.WRITE_EXTERNAL_STORAGE +import android.app.Activity import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -29,10 +30,11 @@ import eu.kanade.tachiyomi.ui.main.SearchActivity import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersController import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController import eu.kanade.tachiyomi.ui.manga.track.TrackController -import kotlinx.android.synthetic.main.search_activity.sTabs +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.toast -import kotlinx.android.synthetic.main.main_activity.tabs -import kotlinx.android.synthetic.main.manga_controller.manga_pager +import kotlinx.android.synthetic.main.main_activity.* +import kotlinx.android.synthetic.main.manga_controller.* +import kotlinx.android.synthetic.main.search_activity.* import rx.Subscription import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -98,6 +100,8 @@ class MangaController : RxController, TabbedController { var startingChapterYPos:Float? = null + var isLockedFromSearch = false + private var adapter: MangaDetailAdapter? = null val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false) @@ -131,6 +135,9 @@ class MangaController : RxController, TabbedController { manga_pager.offscreenPageLimit = 3 manga_pager.adapter = adapter + isLockedFromSearch = activity is SearchActivity && + SecureActivityDelegate.shouldBeLocked() + if (!fromCatalogue) manga_pager.currentItem = CHAPTERS_CONTROLLER } @@ -140,6 +147,12 @@ class MangaController : RxController, TabbedController { adapter = null } + override fun onActivityResumed(activity: Activity) { + super.onActivityResumed(activity) + isLockedFromSearch = activity is SearchActivity && + SecureActivityDelegate.shouldBeLocked() + } + override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { super.onChangeStarted(handler, type) if (type.isEnter) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt index fb91361ef2..c5cc1cfc1e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterHolder.kt @@ -8,9 +8,11 @@ import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.gone +import eu.kanade.tachiyomi.util.view.invisible import eu.kanade.tachiyomi.util.view.setVectorCompat +import eu.kanade.tachiyomi.util.view.visible import kotlinx.android.synthetic.main.chapters_item.* -import java.util.* +import java.util.Date class ChapterHolder( private val view: View, @@ -26,7 +28,7 @@ class ChapterHolder( fun bind(item: ChapterItem, manga: Manga) { val chapter = item.chapter - + val isLocked = item.isLocked chapter_title.text = when (manga.displayMode) { Manga.DISPLAY_NUMBER -> { val number = adapter.decimalFormat.format(chapter.chapter_number.toDouble()) @@ -35,12 +37,16 @@ class ChapterHolder( else -> chapter.name } + chapter_menu.visible() // Set the correct drawable for dropdown and update the tint to match theme. chapter_menu.setVectorCompat(R.drawable.ic_more_vert_black_24dp, view.context.getResourceColor(R.attr.icon_color)) + if (isLocked) chapter_menu.invisible() + // Set correct text color - chapter_title.setTextColor(if (chapter.read) adapter.readColor else adapter.unreadColor) - if (chapter.bookmark) chapter_title.setTextColor(adapter.bookmarkedColor) + chapter_title.setTextColor(if (chapter.read && !isLocked) + adapter.readColor else adapter.unreadColor) + if (chapter.bookmark && !isLocked) chapter_title.setTextColor(adapter.bookmarkedColor) if (chapter.date_upload > 0) { chapter_date.text = adapter.dateFormat.format(Date(chapter.date_upload)) @@ -59,7 +65,7 @@ class ChapterHolder( chapter_title.maxLines = 1 } - chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0) { + chapter_pages.text = if (!chapter.read && chapter.last_page_read > 0 && !isLocked) { itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1) } else { "" @@ -81,6 +87,10 @@ class ChapterHolder( private fun showPopupMenu(view: View) { val item = adapter.getItem(adapterPosition) ?: return + if (item.isLocked) { + adapter.unlock() + return + } // Create a PopupMenu, giving it the clicked view for an anchor val popup = PopupMenu(view.context, view) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt index 141bbdc783..c8b3d768ae 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterItem.kt @@ -14,6 +14,7 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem Chapter by chapter { private var _status: Int = 0 + var isLocked = false var status: Int get() = download?.status ?: _status diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt index 2138a2ba78..2e06f365da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersAdapter.kt @@ -2,18 +2,20 @@ package eu.kanade.tachiyomi.ui.manga.chapter import android.content.Context import android.view.MenuItem +import androidx.fragment.app.FragmentActivity import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.getResourceColor +import uy.kohesive.injekt.injectLazy import java.text.DateFormat import java.text.DecimalFormat import java.text.DecimalFormatSymbols -import eu.kanade.tachiyomi.data.preference.PreferencesHelper -import eu.kanade.tachiyomi.data.preference.getOrDefault -import uy.kohesive.injekt.injectLazy class ChaptersAdapter( - controller: ChaptersController, + val controller: ChaptersController, context: Context ) : FlexibleAdapter(null, controller, true) { @@ -43,8 +45,12 @@ class ChaptersAdapter( return items.indexOf(item) } + fun unlock() { + val activity = controller.activity as? FragmentActivity ?: return + SecureActivityDelegate.promptLockIfNeeded(activity) + } + interface OnMenuItemClickListener { fun onMenuItemClick(position: Int, item: MenuItem) } - } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt index 4b7479ade0..018eb26fb3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersController.kt @@ -25,12 +25,12 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.SearchActivity import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.reader.ReaderActivity +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.getCoordinates @@ -95,6 +95,7 @@ class ChaptersController() : NucleusController(), // Init RecyclerView and adapter adapter = ChaptersAdapter(this, view.context) + setReadingDrawable() recycler.adapter = adapter recycler.layoutManager = LinearLayoutManager(view.context) @@ -117,6 +118,10 @@ class ChaptersController() : NucleusController(), swipe_refresh.refreshes().subscribeUntilDestroy { fetchChaptersFromSource() } fab.clicks().subscribeUntilDestroy { + if (activity is SearchActivity && presenter.isLockedFromSearch) { + SecureActivityDelegate.promptLockIfNeeded(activity) + return@subscribeUntilDestroy + } val item = presenter.getNextUnreadChapter() if (item != null) { // Create animation listener @@ -149,9 +154,29 @@ class ChaptersController() : NucleusController(), actionMode = null super.onDestroyView(view) } + /** + * Update FAB with correct drawable. + * + * @param isFavorite determines if manga is favorite or not. + */ + private fun setReadingDrawable() { + // Set the Favorite drawable to the correct one. + // Border drawable if false, filled drawable if true. + fab.setImageResource( + when { + (parentController as MangaController).isLockedFromSearch -> R.drawable.ic_lock_white_24dp + else -> R.drawable.ic_play_arrow_white_24dp + } + ) + } override fun onActivityResumed(activity: Activity) { + super.onActivityResumed(activity) if (view == null) return + if (activity is SearchActivity) { + presenter.updateLockStatus() + setReadingDrawable() + } // Check if animation view is visible if (reveal_view.visibility == View.VISIBLE) { @@ -159,11 +184,11 @@ class ChaptersController() : NucleusController(), val coordinates = fab.getCoordinates() reveal_view.hideRevealEffect(coordinates.x, coordinates.y, 1920) } - super.onActivityResumed(activity) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { - inflater.inflate(R.menu.chapters, menu) + if (!(parentController as MangaController).isLockedFromSearch) + inflater.inflate(R.menu.chapters, menu) } override fun onPrepareOptionsMenu(menu: Menu) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index dfc4867972..44b5b078c3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed import rx.Observable @@ -65,14 +66,28 @@ class ChaptersPresenter( */ private var observeDownloadsSubscription: Subscription? = null + var isLockedFromSearch = false + + fun updateLockStatus() { + val lastCheck = isLockedFromSearch + isLockedFromSearch = SecureActivityDelegate.shouldBeLocked() + if (lastCheck && lastCheck != isLockedFromSearch) { + chapters.forEach { + it.isLocked = false + } + chaptersRelay.call(chapters) + } + } + override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) + isLockedFromSearch = SecureActivityDelegate.shouldBeLocked() // Prepare the relay. chaptersRelay.flatMap { applyChapterFilters(it) } .observeOn(AndroidSchedulers.mainThread()) - .subscribeLatestCache(ChaptersController::onNextChapters, - { _, error -> Timber.e(error) }) + .subscribeLatestCache(ChaptersController::onNextChapters + ) { _, error -> Timber.e(error) } // Add the subscription that retrieves the chapters from the database, keeps subscribed to // changes, and sends the list of chapters to the relay. @@ -120,6 +135,7 @@ class ChaptersPresenter( private fun Chapter.toModel(): ChapterItem { // Create the model object. val model = ChapterItem(this, manga) + model.isLocked = isLockedFromSearch // Find an active download for this chapter. val download = downloadManager.queue.find { it.chapter.id == id } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 7ef2fec77d..87ba0efea7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -4,6 +4,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.AnimatorSet import android.animation.ObjectAnimator +import android.app.Activity import android.app.Dialog import android.app.PendingIntent import android.content.ClipData @@ -61,26 +62,19 @@ import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.main.MainActivity -import eu.kanade.tachiyomi.ui.main.SearchActivity import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.ui.webview.WebViewActivity -import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.storage.getUriCompat +import eu.kanade.tachiyomi.util.system.toast +import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.marginBottom import eu.kanade.tachiyomi.util.view.snack -import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updatePaddingRelative import jp.wasabeef.glide.transformations.CropSquareTransformation import jp.wasabeef.glide.transformations.MaskTransformation -import kotlinx.android.synthetic.main.edit_manga_dialog.* import kotlinx.android.synthetic.main.manga_info_controller.* -import kotlinx.android.synthetic.main.manga_info_controller.manga_artist -import kotlinx.android.synthetic.main.manga_info_controller.manga_author -import kotlinx.android.synthetic.main.manga_info_controller.manga_cover -import kotlinx.android.synthetic.main.manga_info_controller.manga_genres_tags -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.io.File import java.text.DateFormat @@ -238,7 +232,8 @@ class MangaInfoController : NucleusController(), inflater.inflate(R.menu.manga_info, menu) val editItem = menu.findItem(R.id.action_edit) - editItem.isVisible = presenter.manga.favorite + editItem.isVisible = presenter.manga.favorite && + !(parentController as MangaController).isLockedFromSearch } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -365,6 +360,11 @@ class MangaInfoController : NucleusController(), } } + override fun onActivityResumed(activity: Activity) { + super.onActivityResumed(activity) + setFavoriteDrawable(presenter.manga.favorite) + } + override fun onDestroyView(view: View) { manga_genres_tags.setOnTagClickListener(null) snack?.dismiss() @@ -466,10 +466,13 @@ class MangaInfoController : NucleusController(), private fun setFavoriteDrawable(isFavorite: Boolean) { // Set the Favorite drawable to the correct one. // Border drawable if false, filled drawable if true. - fab_favorite?.setImageResource(if (isFavorite) - R.drawable.ic_bookmark_white_24dp - else - R.drawable.ic_add_to_library_24dp) + fab_favorite?.setImageResource( + when { + (parentController as MangaController).isLockedFromSearch -> R.drawable.ic_lock_white_24dp + isFavorite -> R.drawable.ic_bookmark_white_24dp + else -> R.drawable.ic_add_to_library_24dp + } + ) } /** @@ -510,6 +513,10 @@ class MangaInfoController : NucleusController(), * Called when the fab is clicked. */ private fun onFabClick() { + if ((parentController as MangaController).isLockedFromSearch) { + SecureActivityDelegate.promptLockIfNeeded(activity) + return + } val manga = presenter.manga toggleFavorite() if (manga.favorite) { @@ -689,6 +696,8 @@ class MangaInfoController : NucleusController(), * @param query the search query to pass to the search controller */ private fun performGlobalSearch(query: String) { + if ((parentController as MangaController).isLockedFromSearch) + return val router = parentController?.router ?: return router.pushController(CatalogueSearchController(query).withFadeTransaction()) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt index 84cbc43192..71f89f09c0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackController.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.manga.track +import android.app.Activity import android.content.Intent import android.net.Uri import android.view.LayoutInflater @@ -11,8 +12,12 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener +import eu.kanade.tachiyomi.util.view.gone +import eu.kanade.tachiyomi.util.view.invisible +import eu.kanade.tachiyomi.util.view.visible import kotlinx.android.synthetic.main.track_controller.* import timber.log.Timber @@ -41,13 +46,31 @@ class TrackController : NucleusController(), override fun onViewCreated(view: View) { super.onViewCreated(view) + if ((parentController as MangaController).isLockedFromSearch) { + swipe_refresh.invisible() + unlock_button.visible() + unlock_button.setOnClickListener { + SecureActivityDelegate.promptLockIfNeeded(activity) + } + } + adapter = TrackAdapter(this) - with(view) { - track_recycler.layoutManager = LinearLayoutManager(context) - track_recycler.adapter = adapter - track_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) - swipe_refresh.isEnabled = false - swipe_refresh.refreshes().subscribeUntilDestroy { presenter.refresh() } + track_recycler.layoutManager = LinearLayoutManager(view.context) + track_recycler.adapter = adapter + track_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) + swipe_refresh.isEnabled = false + swipe_refresh.refreshes().subscribeUntilDestroy { presenter.refresh() } + } + + private fun showTracking() { + swipe_refresh.visible() + unlock_button.gone() + } + + override fun onActivityResumed(activity: Activity) { + super.onActivityResumed(activity) + if (!(parentController as MangaController).isLockedFromSearch) { + showTracking() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 43e749fef4..58515f665f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -21,7 +21,6 @@ import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.SeekBar import androidx.appcompat.app.AppCompatDelegate -import androidx.biometric.BiometricManager import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.google.android.material.bottomsheet.BottomSheetDialog import eu.kanade.tachiyomi.R @@ -32,8 +31,6 @@ import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity -import eu.kanade.tachiyomi.ui.main.BiometricActivity -import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success @@ -45,11 +42,12 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.L2RPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer -import eu.kanade.tachiyomi.util.system.launchUI +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.lang.plusAssign import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.visible @@ -67,7 +65,6 @@ import rx.subscriptions.CompositeSubscription import timber.log.Timber import uy.kohesive.injekt.injectLazy import java.io.File -import java.util.Date import java.util.concurrent.TimeUnit import kotlin.math.abs @@ -527,23 +524,6 @@ class ReaderActivity : BaseRxActivity(), presenter.shareImage(page) } - override fun onResume() { - super.onResume() - val useBiometrics = preferences.useBiometrics().getOrDefault() - if (useBiometrics && BiometricManager.from(this) - .canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) { - if (!MainActivity.unlocked && (preferences.lockAfter().getOrDefault() <= 0 || Date() - .time >= - preferences.lastUnlock().getOrDefault() + 60 * 1000 * preferences.lockAfter().getOrDefault())) { - val intent = Intent(this, BiometricActivity::class.java) - startActivity(intent) - this.overridePendingTransition(0, 0) - } - } - else if (useBiometrics) - preferences.useBiometrics().set(false) - } - /** * Called from the presenter when a page is ready to be shared. It shows Android's default * sharing tool. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/BiometricActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/security/BiometricActivity.kt similarity index 73% rename from app/src/main/java/eu/kanade/tachiyomi/ui/main/BiometricActivity.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/security/BiometricActivity.kt index b630cb849d..067e2e4784 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/BiometricActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/security/BiometricActivity.kt @@ -1,16 +1,15 @@ -package eu.kanade.tachiyomi.ui.main +package eu.kanade.tachiyomi.ui.security import android.os.Bundle import androidx.biometric.BiometricPrompt import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.ui.base.activity.BaseActivity -import uy.kohesive.injekt.injectLazy import java.util.Date +import java.util.concurrent.ExecutorService import java.util.concurrent.Executors class BiometricActivity : BaseActivity() { - val executor = Executors.newSingleThreadExecutor() + private val executor: ExecutorService = Executors.newSingleThreadExecutor() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -24,15 +23,10 @@ class BiometricActivity : BaseActivity() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) - MainActivity.unlocked = true + SecureActivityDelegate.locked = false preferences.lastUnlock().set(Date().time) finish() } - - override fun onAuthenticationFailed() { - super.onAuthenticationFailed() - // TODO("Called when a biometric is valid but not recognized.") - } }) val promptInfo = BiometricPrompt.PromptInfo.Builder() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt new file mode 100644 index 0000000000..e13ff2b81f --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/security/SecureActivityDelegate.kt @@ -0,0 +1,57 @@ +package eu.kanade.tachiyomi.ui.security + +import android.app.Activity +import android.content.Intent +import android.view.WindowManager +import androidx.biometric.BiometricManager +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.getOrDefault +import uy.kohesive.injekt.injectLazy +import java.util.Date + +object SecureActivityDelegate { + + private val preferences by injectLazy() + + var locked: Boolean = true + + fun setSecure(activity: Activity?, force:Boolean? = null) { + val enabled = force ?: preferences.secureScreen().getOrDefault() + if (enabled) { + activity?.window?.setFlags( + WindowManager.LayoutParams.FLAG_SECURE, + WindowManager.LayoutParams.FLAG_SECURE + ) + } else { + activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) + } + } + + fun promptLockIfNeeded(activity: Activity?) { + if (activity == null) return + val lockApp = preferences.useBiometrics().getOrDefault() + if (lockApp && BiometricManager.from(activity).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) { + if (isAppLocked()) { + val intent = Intent(activity, BiometricActivity::class.java) + activity.startActivity(intent) + activity.overridePendingTransition(0, 0) + } + } else if (lockApp) { + preferences.useBiometrics().set(false) + } + } + + fun shouldBeLocked(): Boolean { + val lockApp = preferences.useBiometrics().getOrDefault() + if (lockApp && isAppLocked()) return true + return false + } + + private fun isAppLocked(): Boolean { + return locked && + (preferences.lockAfter().getOrDefault() <= 0 + || Date().time >= preferences.lastUnlock().getOrDefault() + 60 * 1000 * preferences + .lockAfter().getOrDefault()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt index ba04203382..c1b7d26a61 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt @@ -1,17 +1,14 @@ package eu.kanade.tachiyomi.ui.setting -import androidx.appcompat.app.AppCompatDelegate import androidx.biometric.BiometricManager import androidx.preference.PreferenceScreen -import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.updater.UpdaterJob -import eu.kanade.tachiyomi.widget.preference.IntListMatPreference +import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.LocaleHelper -import eu.kanade.tachiyomi.ui.main.MainActivity -import kotlinx.android.synthetic.main.main_activity.* +import eu.kanade.tachiyomi.widget.preference.IntListMatPreference import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys class SettingsGeneralController : SettingsController() { @@ -97,35 +94,53 @@ class SettingsGeneralController : SettingsController() { } } - val biometricManager = BiometricManager.from(context) - if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) { - var preference:IntListMatPreference? = null + preferenceCategory { + titleRes = R.string.pref_category_security + + val biometricManager = BiometricManager.from(context) + if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) { + var preference: IntListMatPreference? = null + switchPreference { + key = Keys.useBiometrics + titleRes = R.string.lock_with_biometrics + defaultValue = false + + onChange { + preference?.isVisible = it as Boolean + true + } + } + preference = intListPreference(activity) { + key = Keys.lockAfter + titleRes = R.string.lock_when_idle + isVisible = preferences.useBiometrics().getOrDefault() + val values = listOf(0, 2, 5, 10, 20, 30, 60, 90, 120, -1) + entries = values.mapNotNull { + when (it) { + 0 -> context.getString(R.string.lock_always) + -1 -> context.getString(R.string.lock_never) + else -> resources?.getQuantityString( + R.plurals.lock_after_mins, it.toInt(), it + ) + } + } + entryValues = values + defaultValue = 0 + } + } + switchPreference { - key = Keys.useBiometrics - titleRes = R.string.lock_with_biometrics + key = Keys.secureScreen + titleRes = R.string.pref_secure_screen + summaryRes = R.string.pref_secure_screen_summary defaultValue = false onChange { - preference?.isVisible = it as Boolean + it as Boolean + SecureActivityDelegate.setSecure(activity, it) true } } - preference = intListPreference(activity) { - key = Keys.lockAfter - titleRes = R.string.lock_when_idle - isVisible = preferences.useBiometrics().getOrDefault() - val values = listOf(0, 2, 5, 10, 20, 30, 60, 90, 120, -1) - entries = values.mapNotNull { - when (it) { - 0 -> context.getString(R.string.lock_always) - -1 -> context.getString(R.string.lock_never) - else -> resources?.getQuantityString(R.plurals.lock_after_mins, it.toInt(), - it) - } - } - entryValues = values - defaultValue = 0 - } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index e105962436..8d2a13a14d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -185,7 +185,8 @@ class SettingsLibraryController : SettingsController() { intListPreference(activity) { titleRes = R.string.pref_keep_category_sorting key = Keys.keepCatSort - summaryRes = R.string.pref_keep_category_sorting_summary + + customSummary = context.getString(R.string.pref_keep_category_sorting_summary) entries = listOf( context.getString(R.string.always_ask), context.getString(R.string.option_keep_category_sort), diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt index 0b3b8a37a3..f98bf0966f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListMatPreference.kt @@ -27,7 +27,8 @@ AttributeSet? = defValue = defaultValue as? Int ?: defValue } override fun getSummary(): CharSequence { - if (key == null || useCustomSummary) return super.getSummary() + if (customSummary != null) return customSummary!! + if (key == null) return super.getSummary() val index = entryValues.indexOf(prefs.getInt(key, defValue).getOrDefault()) return if (entries.isEmpty() || index == -1) "" else entries[index] diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt index 8fad529a36..56103c5b5a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ListMatPreference.kt @@ -3,17 +3,11 @@ package eu.kanade.tachiyomi.widget.preference import android.annotation.SuppressLint import android.app.Activity import android.content.Context -import android.content.SharedPreferences import android.util.AttributeSet import androidx.preference.Preference -import androidx.preference.PreferenceManager import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItemsSingleChoice -import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault -import eu.kanade.tachiyomi.ui.setting.defaultValue -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get open class ListMatPreference @JvmOverloads constructor(activity: Activity?, context: Context, attrs: AttributeSet? = @@ -34,6 +28,7 @@ open class ListMatPreference @JvmOverloads constructor(activity: Activity?, cont defValue = defaultValue as? String ?: defValue } override fun getSummary(): CharSequence { + if (customSummary != null) return customSummary!! val index = entryValues.indexOf(prefs.getStringPref(key, defValue).getOrDefault()) return if (entries.isEmpty() || index == -1) "" else entries[index] diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt index 604620ee87..2763838a7b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MatPreference.kt @@ -16,19 +16,10 @@ open class MatPreference @JvmOverloads constructor(val activity: Activity?, cont null) : Preference(context, attrs) { - protected var useCustomSummary = false protected val prefs: PreferencesHelper = Injekt.get() private var isShowing = false + var customSummary:String? = null - override fun setSummary(summaryResId: Int) { - useCustomSummary = true - super.setSummary(summaryResId) - } - - override fun setSummary(summary: CharSequence?) { - useCustomSummary = true - super.setSummary(summary) - } override fun onClick() { if (!isShowing) dialog().apply { @@ -37,6 +28,10 @@ open class MatPreference @JvmOverloads constructor(val activity: Activity?, cont isShowing = true } + override fun getSummary(): CharSequence { + return customSummary ?: super.getSummary() + } + open fun dialog(): MaterialDialog { return MaterialDialog(activity ?: context).apply { if (title != null) diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt index 14ea22b901..406fc0ba0f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/MultiListMatPreference.kt @@ -20,10 +20,15 @@ class MultiListMatPreference @JvmOverloads constructor(activity: Activity?, cont var customSummaryRes:Int get() = 0 set(value) { customSummary = context.getString(value) } - var customSummary:String? = null override fun getSummary(): CharSequence { - return customSummary ?: super.getSummary() + if (customSummary != null) return customSummary!! + return prefs.getStringSet(key, emptySet()).getOrDefault().mapNotNull { + if (entryValues.indexOf(it) == -1) null + else entryValues.indexOf(it) + if (allSelectionRes != null) 1 else 0 + }.toIntArray().joinToString(",") { + entries[it] + } } @SuppressLint("CheckResult") diff --git a/app/src/main/res/drawable/ic_lock_white_24dp.xml b/app/src/main/res/drawable/ic_lock_white_24dp.xml new file mode 100644 index 0000000000..146c066b5f --- /dev/null +++ b/app/src/main/res/drawable/ic_lock_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/track_controller.xml b/app/src/main/res/layout/track_controller.xml index b0a35bb1b9..bfff5494c2 100644 --- a/app/src/main/res/layout/track_controller.xml +++ b/app/src/main/res/layout/track_controller.xml @@ -1,8 +1,10 @@ - + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + - + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 28076c7a04..080bef7e0b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -48,6 +48,7 @@ Hiding categories Manga only Manwha only + Unlock to access trackers Sorting by %1$s Sort by: %1$s @@ -169,6 +170,9 @@ Date format Check for updates Automatically check for new app versions + Secure screen + Hide Tachiyomi from the overview screen + Security Display