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 <arkon@users.noreply.github.com>
pull/3117/head
Jay 5 years ago
parent bfec83440c
commit 243bffebf9

@ -62,7 +62,7 @@
android:name=".ui.webview.WebViewActivity"
android:configChanges="uiMode|orientation|screenSize"/>
<activity
android:name=".ui.main.BiometricActivity" />
android:name=".ui.security.BiometricActivity" />
<activity
android:name=".widget.CustomLayoutPickerActivity"
android:label="@string/app_name"

@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.updater.UpdaterJob
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.LocaleHelper
import org.acra.ACRA
import org.acra.annotation.ReportsCrashes
@ -55,7 +55,7 @@ open class App : Application(), LifecycleObserver {
//App in background
val preferences: PreferencesHelper by injectLazy()
if (preferences.lockAfter().getOrDefault() >= 0) {
MainActivity.unlocked = false
SecureActivityDelegate.locked = true
}
}

@ -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"

@ -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)

@ -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)
}
}

@ -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<P : BasePresenter<*>> : NucleusAppCompatActivity<P
LocaleHelper.updateConfiguration(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SecureActivityDelegate.setSecure(this)
}
override fun onResume() {
super.onResume()
SecureActivityDelegate.promptLockIfNeeded(this)
}
}

@ -16,7 +16,6 @@ import android.webkit.WebView
import android.widget.FrameLayout
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import androidx.biometric.BiometricManager
import androidx.core.view.GravityCompat
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Conductor
@ -53,6 +52,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI
@ -353,22 +353,10 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
override fun onResume() {
super.onResume()
// setting in case someone comes from the search activity
// setting in case someone comes from the search activity to main
usingBottomNav = true
getExtensionUpdates()
DownloadService.callListeners()
val useBiometrics = preferences.useBiometrics().getOrDefault()
if (useBiometrics && BiometricManager.from(this)
.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
if (!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)
}
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
}

@ -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()
}
}

@ -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) {

@ -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)

@ -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

@ -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<ChapterItem>(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)
}
}

@ -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<ChaptersPresenter>(),
// 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<ChaptersPresenter>(),
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<ChaptersPresenter>(),
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,10 +184,10 @@ class ChaptersController() : NucleusController<ChaptersPresenter>(),
val coordinates = fab.getCoordinates()
reveal_view.hideRevealEffect(coordinates.x, coordinates.y, 1920)
}
super.onActivityResumed(activity)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
if (!(parentController as MangaController).isLockedFromSearch)
inflater.inflate(R.menu.chapters, menu)
}

@ -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 }

@ -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<MangaInfoPresenter>(),
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<MangaInfoPresenter>(),
}
}
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<MangaInfoPresenter>(),
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<MangaInfoPresenter>(),
* 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<MangaInfoPresenter>(),
* @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())
}

@ -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,14 +46,32 @@ class TrackController : NucleusController<TrackPresenter>(),
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.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()
}
}
override fun onDestroyView(view: View) {

@ -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<ReaderPresenter>(),
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.

@ -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()

@ -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<PreferencesHelper>()
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())
}
}

@ -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,6 +94,9 @@ class SettingsGeneralController : SettingsController() {
}
}
preferenceCategory {
titleRes = R.string.pref_category_security
val biometricManager = BiometricManager.from(context)
if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
var preference: IntListMatPreference? = null
@ -119,13 +119,28 @@ class SettingsGeneralController : SettingsController() {
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)
else -> resources?.getQuantityString(
R.plurals.lock_after_mins, it.toInt(), it
)
}
}
entryValues = values
defaultValue = 0
}
}
switchPreference {
key = Keys.secureScreen
titleRes = R.string.pref_secure_screen
summaryRes = R.string.pref_secure_screen_summary
defaultValue = false
onChange {
it as Boolean
SecureActivityDelegate.setSecure(activity, it)
true
}
}
}
}
}

@ -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),

@ -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]

@ -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]

@ -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)

@ -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<String>()).getOrDefault().mapNotNull {
if (entryValues.indexOf(it) == -1) null
else entryValues.indexOf(it) + if (allSelectionRes != null) 1 else 0
}.toIntArray().joinToString(",") {
entries[it]
}
}
@SuppressLint("CheckResult")

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/>
</vector>

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
@ -20,4 +22,17 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/unlock_button"
android:layout_width="wrap_content"
android:layout_marginTop="30dp"
android:layout_gravity="center|top"
app:rippleColor="@color/fullRippleColor"
android:visibility="gone"
tools:visibility="visible"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:textColor="?android:attr/colorAccent"
android:layout_height="wrap_content"
android:text="@string/action_unlock_trackers"/>
</FrameLayout>

@ -48,6 +48,7 @@
<string name="hiding_categories">Hiding categories</string>
<string name="manga_only">Manga only</string>
<string name="manwha_only">Manwha only</string>
<string name="action_unlock_trackers">Unlock to access trackers</string>
<string name="sorting_by_">Sorting by %1$s</string>
<string name="sort_by_">Sort by: %1$s</string>
@ -169,6 +170,9 @@
<string name="pref_date_format">Date format</string>
<string name="pref_enable_automatic_updates">Check for updates</string>
<string name="pref_enable_automatic_updates_summary">Automatically check for new app versions</string>
<string name="pref_secure_screen">Secure screen</string>
<string name="pref_secure_screen_summary">Hide Tachiyomi from the overview screen</string>
<string name="pref_category_security">Security</string>
<!-- Library section -->
<string name="pref_category_library_display">Display</string>

Loading…
Cancel
Save