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>
This commit is contained in:
Jay 2020-02-22 17:05:39 -08:00
parent bfec83440c
commit 243bffebf9
28 changed files with 324 additions and 142 deletions

View File

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

View File

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

View File

@ -131,6 +131,8 @@ object PreferenceKeys {
const val lastUnlock = "last_unlock" const val lastUnlock = "last_unlock"
const val secureScreen = "secure_screen"
const val removeArticles = "remove_articles" const val removeArticles = "remove_articles"
const val skipPreMigration = "skip_pre_migration" const val skipPreMigration = "skip_pre_migration"

View File

@ -215,6 +215,8 @@ class PreferencesHelper(val context: Context) {
fun lastUnlock() = rxPrefs.getLong(Keys.lastUnlock, 0) fun lastUnlock() = rxPrefs.getLong(Keys.lastUnlock, 0)
fun secureScreen() = rxPrefs.getBoolean(Keys.secureScreen, false)
fun removeArticles() = rxPrefs.getBoolean(Keys.removeArticles, false) fun removeArticles() = rxPrefs.getBoolean(Keys.removeArticles, false)
fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE) fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE)

View File

@ -5,6 +5,9 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper 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 eu.kanade.tachiyomi.util.system.LocaleHelper
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -32,6 +35,12 @@ abstract class BaseActivity : AppCompatActivity() {
else -> R.style.Theme_Tachiyomi else -> R.style.Theme_Tachiyomi
}) })
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
SecureActivityDelegate.setSecure(this)
} }
override fun onResume() {
super.onResume()
if (this !is BiometricActivity && this !is SearchActivity)
SecureActivityDelegate.promptLockIfNeeded(this)
}
} }

View File

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.ui.base.activity package eu.kanade.tachiyomi.ui.base.activity
import android.os.Bundle
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import nucleus.view.NucleusAppCompatActivity import nucleus.view.NucleusAppCompatActivity
@ -11,4 +13,14 @@ abstract class BaseRxActivity<P : BasePresenter<*>> : NucleusAppCompatActivity<P
LocaleHelper.updateConfiguration(this) LocaleHelper.updateConfiguration(this)
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
SecureActivityDelegate.setSecure(this)
}
override fun onResume() {
super.onResume()
SecureActivityDelegate.promptLockIfNeeded(this)
}
} }

View File

@ -16,7 +16,6 @@ import android.webkit.WebView
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.graphics.drawable.DrawerArrowDrawable import androidx.appcompat.graphics.drawable.DrawerArrowDrawable
import androidx.biometric.BiometricManager
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.Conductor 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.migration.manga.process.MigrationListController
import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController import eu.kanade.tachiyomi.ui.recent_updates.RecentChaptersController
import eu.kanade.tachiyomi.ui.recently_read.RecentlyReadController 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.ui.setting.SettingsMainController
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
@ -353,22 +353,10 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
override fun onResume() { override fun onResume() {
super.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 usingBottomNav = true
getExtensionUpdates() getExtensionUpdates()
DownloadService.callListeners() 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() { private fun getExtensionUpdates() {
@ -480,7 +468,7 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
val baseController = router.backstack.last().controller() as? BaseController val baseController = router.backstack.last().controller() as? BaseController
if (if (router.backstackSize == 1) !(baseController?.handleRootBack() ?: false) if (if (router.backstackSize == 1) !(baseController?.handleRootBack() ?: false)
else !router.handleBack()) { else !router.handleBack()) {
unlocked = false SecureActivityDelegate.locked = true
super.onBackPressed() super.onBackPressed()
} }
} }
@ -593,8 +581,6 @@ open class MainActivity : BaseActivity(), DownloadServiceListener {
private const val URL_HELP = "https://tachiyomi.org/help/" private const val URL_HELP = "https://tachiyomi.org/help/"
var unlocked = false
var usingBottomNav = true var usingBottomNav = true
internal set internal set
} }

View File

@ -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.TabbedController
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.catalogue.global_search.CatalogueSearchController 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.system.getResourceColor
import eu.kanade.tachiyomi.util.view.updateLayoutParams import eu.kanade.tachiyomi.util.view.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePadding import eu.kanade.tachiyomi.util.view.updatePadding
@ -148,7 +149,7 @@ class SearchActivity: MainActivity() {
override fun onBackPressed() { override fun onBackPressed() {
if (router.backstack.size <= 1 || !router.handleBack()) { if (router.backstack.size <= 1 || !router.handleBack()) {
unlocked = false SecureActivityDelegate.locked = true
super.onBackPressed() super.onBackPressed()
} }
} }

View File

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.ui.manga package eu.kanade.tachiyomi.ui.manga
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import android.app.Activity
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View 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.chapter.ChaptersController
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController
import eu.kanade.tachiyomi.ui.manga.track.TrackController 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 eu.kanade.tachiyomi.util.system.toast
import kotlinx.android.synthetic.main.main_activity.tabs import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.manga_controller.manga_pager import kotlinx.android.synthetic.main.manga_controller.*
import kotlinx.android.synthetic.main.search_activity.*
import rx.Subscription import rx.Subscription
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -98,6 +100,8 @@ class MangaController : RxController, TabbedController {
var startingChapterYPos:Float? = null var startingChapterYPos:Float? = null
var isLockedFromSearch = false
private var adapter: MangaDetailAdapter? = null private var adapter: MangaDetailAdapter? = null
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false) val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
@ -131,6 +135,9 @@ class MangaController : RxController, TabbedController {
manga_pager.offscreenPageLimit = 3 manga_pager.offscreenPageLimit = 3
manga_pager.adapter = adapter manga_pager.adapter = adapter
isLockedFromSearch = activity is SearchActivity &&
SecureActivityDelegate.shouldBeLocked()
if (!fromCatalogue) if (!fromCatalogue)
manga_pager.currentItem = CHAPTERS_CONTROLLER manga_pager.currentItem = CHAPTERS_CONTROLLER
} }
@ -140,6 +147,12 @@ class MangaController : RxController, TabbedController {
adapter = null adapter = null
} }
override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
isLockedFromSearch = activity is SearchActivity &&
SecureActivityDelegate.shouldBeLocked()
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) { override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type) super.onChangeStarted(handler, type)
if (type.isEnter) { if (type.isEnter) {

View File

@ -8,9 +8,11 @@ import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.view.gone 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.setVectorCompat
import eu.kanade.tachiyomi.util.view.visible
import kotlinx.android.synthetic.main.chapters_item.* import kotlinx.android.synthetic.main.chapters_item.*
import java.util.* import java.util.Date
class ChapterHolder( class ChapterHolder(
private val view: View, private val view: View,
@ -26,7 +28,7 @@ class ChapterHolder(
fun bind(item: ChapterItem, manga: Manga) { fun bind(item: ChapterItem, manga: Manga) {
val chapter = item.chapter val chapter = item.chapter
val isLocked = item.isLocked
chapter_title.text = when (manga.displayMode) { chapter_title.text = when (manga.displayMode) {
Manga.DISPLAY_NUMBER -> { Manga.DISPLAY_NUMBER -> {
val number = adapter.decimalFormat.format(chapter.chapter_number.toDouble()) val number = adapter.decimalFormat.format(chapter.chapter_number.toDouble())
@ -35,12 +37,16 @@ class ChapterHolder(
else -> chapter.name else -> chapter.name
} }
chapter_menu.visible()
// Set the correct drawable for dropdown and update the tint to match theme. // 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)) 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 // Set correct text color
chapter_title.setTextColor(if (chapter.read) adapter.readColor else adapter.unreadColor) chapter_title.setTextColor(if (chapter.read && !isLocked)
if (chapter.bookmark) chapter_title.setTextColor(adapter.bookmarkedColor) adapter.readColor else adapter.unreadColor)
if (chapter.bookmark && !isLocked) chapter_title.setTextColor(adapter.bookmarkedColor)
if (chapter.date_upload > 0) { if (chapter.date_upload > 0) {
chapter_date.text = adapter.dateFormat.format(Date(chapter.date_upload)) chapter_date.text = adapter.dateFormat.format(Date(chapter.date_upload))
@ -59,7 +65,7 @@ class ChapterHolder(
chapter_title.maxLines = 1 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) itemView.context.getString(R.string.chapter_progress, chapter.last_page_read + 1)
} else { } else {
"" ""
@ -81,6 +87,10 @@ class ChapterHolder(
private fun showPopupMenu(view: View) { private fun showPopupMenu(view: View) {
val item = adapter.getItem(adapterPosition) ?: return val item = adapter.getItem(adapterPosition) ?: return
if (item.isLocked) {
adapter.unlock()
return
}
// Create a PopupMenu, giving it the clicked view for an anchor // Create a PopupMenu, giving it the clicked view for an anchor
val popup = PopupMenu(view.context, view) val popup = PopupMenu(view.context, view)

View File

@ -14,6 +14,7 @@ class ChapterItem(val chapter: Chapter, val manga: Manga) : AbstractFlexibleItem
Chapter by chapter { Chapter by chapter {
private var _status: Int = 0 private var _status: Int = 0
var isLocked = false
var status: Int var status: Int
get() = download?.status ?: _status get() = download?.status ?: _status

View File

@ -2,18 +2,20 @@ package eu.kanade.tachiyomi.ui.manga.chapter
import android.content.Context import android.content.Context
import android.view.MenuItem import android.view.MenuItem
import androidx.fragment.app.FragmentActivity
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R 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 eu.kanade.tachiyomi.util.system.getResourceColor
import uy.kohesive.injekt.injectLazy
import java.text.DateFormat import java.text.DateFormat
import java.text.DecimalFormat import java.text.DecimalFormat
import java.text.DecimalFormatSymbols 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( class ChaptersAdapter(
controller: ChaptersController, val controller: ChaptersController,
context: Context context: Context
) : FlexibleAdapter<ChapterItem>(null, controller, true) { ) : FlexibleAdapter<ChapterItem>(null, controller, true) {
@ -43,8 +45,12 @@ class ChaptersAdapter(
return items.indexOf(item) return items.indexOf(item)
} }
fun unlock() {
val activity = controller.activity as? FragmentActivity ?: return
SecureActivityDelegate.promptLockIfNeeded(activity)
}
interface OnMenuItemClickListener { interface OnMenuItemClickListener {
fun onMenuItemClick(position: Int, item: MenuItem) fun onMenuItemClick(position: Int, item: MenuItem)
} }
} }

View File

@ -25,12 +25,12 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download 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.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.main.SearchActivity import eu.kanade.tachiyomi.ui.main.SearchActivity
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity 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.system.toast
import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets import eu.kanade.tachiyomi.util.view.doOnApplyWindowInsets
import eu.kanade.tachiyomi.util.view.getCoordinates import eu.kanade.tachiyomi.util.view.getCoordinates
@ -95,6 +95,7 @@ class ChaptersController() : NucleusController<ChaptersPresenter>(),
// Init RecyclerView and adapter // Init RecyclerView and adapter
adapter = ChaptersAdapter(this, view.context) adapter = ChaptersAdapter(this, view.context)
setReadingDrawable()
recycler.adapter = adapter recycler.adapter = adapter
recycler.layoutManager = LinearLayoutManager(view.context) recycler.layoutManager = LinearLayoutManager(view.context)
@ -117,6 +118,10 @@ class ChaptersController() : NucleusController<ChaptersPresenter>(),
swipe_refresh.refreshes().subscribeUntilDestroy { fetchChaptersFromSource() } swipe_refresh.refreshes().subscribeUntilDestroy { fetchChaptersFromSource() }
fab.clicks().subscribeUntilDestroy { fab.clicks().subscribeUntilDestroy {
if (activity is SearchActivity && presenter.isLockedFromSearch) {
SecureActivityDelegate.promptLockIfNeeded(activity)
return@subscribeUntilDestroy
}
val item = presenter.getNextUnreadChapter() val item = presenter.getNextUnreadChapter()
if (item != null) { if (item != null) {
// Create animation listener // Create animation listener
@ -149,9 +154,29 @@ class ChaptersController() : NucleusController<ChaptersPresenter>(),
actionMode = null actionMode = null
super.onDestroyView(view) 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) { override fun onActivityResumed(activity: Activity) {
super.onActivityResumed(activity)
if (view == null) return if (view == null) return
if (activity is SearchActivity) {
presenter.updateLockStatus()
setReadingDrawable()
}
// Check if animation view is visible // Check if animation view is visible
if (reveal_view.visibility == View.VISIBLE) { if (reveal_view.visibility == View.VISIBLE) {
@ -159,11 +184,11 @@ class ChaptersController() : NucleusController<ChaptersPresenter>(),
val coordinates = fab.getCoordinates() val coordinates = fab.getCoordinates()
reveal_view.hideRevealEffect(coordinates.x, coordinates.y, 1920) reveal_view.hideRevealEffect(coordinates.x, coordinates.y, 1920)
} }
super.onActivityResumed(activity)
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 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) { override fun onPrepareOptionsMenu(menu: Menu) {

View File

@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter 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.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed
import rx.Observable import rx.Observable
@ -65,14 +66,28 @@ class ChaptersPresenter(
*/ */
private var observeDownloadsSubscription: Subscription? = null 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?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
isLockedFromSearch = SecureActivityDelegate.shouldBeLocked()
// Prepare the relay. // Prepare the relay.
chaptersRelay.flatMap { applyChapterFilters(it) } chaptersRelay.flatMap { applyChapterFilters(it) }
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeLatestCache(ChaptersController::onNextChapters, .subscribeLatestCache(ChaptersController::onNextChapters
{ _, error -> Timber.e(error) }) ) { _, error -> Timber.e(error) }
// Add the subscription that retrieves the chapters from the database, keeps subscribed to // Add the subscription that retrieves the chapters from the database, keeps subscribed to
// changes, and sends the list of chapters to the relay. // changes, and sends the list of chapters to the relay.
@ -120,6 +135,7 @@ class ChaptersPresenter(
private fun Chapter.toModel(): ChapterItem { private fun Chapter.toModel(): ChapterItem {
// Create the model object. // Create the model object.
val model = ChapterItem(this, manga) val model = ChapterItem(this, manga)
model.isLocked = isLockedFromSearch
// Find an active download for this chapter. // Find an active download for this chapter.
val download = downloadManager.queue.find { it.chapter.id == id } val download = downloadManager.queue.find { it.chapter.id == id }

View File

@ -4,6 +4,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.app.PendingIntent import android.app.PendingIntent
import android.content.ClipData 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.ChangeMangaCategoriesDialog
import eu.kanade.tachiyomi.ui.library.LibraryController import eu.kanade.tachiyomi.ui.library.LibraryController
import eu.kanade.tachiyomi.ui.main.MainActivity 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.manga.MangaController
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.webview.WebViewActivity 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.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.marginBottom
import eu.kanade.tachiyomi.util.view.snack 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.updateLayoutParams
import eu.kanade.tachiyomi.util.view.updatePaddingRelative import eu.kanade.tachiyomi.util.view.updatePaddingRelative
import jp.wasabeef.glide.transformations.CropSquareTransformation import jp.wasabeef.glide.transformations.CropSquareTransformation
import jp.wasabeef.glide.transformations.MaskTransformation 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.*
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 uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.text.DateFormat import java.text.DateFormat
@ -238,7 +232,8 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
inflater.inflate(R.menu.manga_info, menu) inflater.inflate(R.menu.manga_info, menu)
val editItem = menu.findItem(R.id.action_edit) 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 { 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) { override fun onDestroyView(view: View) {
manga_genres_tags.setOnTagClickListener(null) manga_genres_tags.setOnTagClickListener(null)
snack?.dismiss() snack?.dismiss()
@ -466,10 +466,13 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
private fun setFavoriteDrawable(isFavorite: Boolean) { private fun setFavoriteDrawable(isFavorite: Boolean) {
// Set the Favorite drawable to the correct one. // Set the Favorite drawable to the correct one.
// Border drawable if false, filled drawable if true. // Border drawable if false, filled drawable if true.
fab_favorite?.setImageResource(if (isFavorite) fab_favorite?.setImageResource(
R.drawable.ic_bookmark_white_24dp when {
else (parentController as MangaController).isLockedFromSearch -> R.drawable.ic_lock_white_24dp
R.drawable.ic_add_to_library_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. * Called when the fab is clicked.
*/ */
private fun onFabClick() { private fun onFabClick() {
if ((parentController as MangaController).isLockedFromSearch) {
SecureActivityDelegate.promptLockIfNeeded(activity)
return
}
val manga = presenter.manga val manga = presenter.manga
toggleFavorite() toggleFavorite()
if (manga.favorite) { if (manga.favorite) {
@ -689,6 +696,8 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
* @param query the search query to pass to the search controller * @param query the search query to pass to the search controller
*/ */
private fun performGlobalSearch(query: String) { private fun performGlobalSearch(query: String) {
if ((parentController as MangaController).isLockedFromSearch)
return
val router = parentController?.router ?: return val router = parentController?.router ?: return
router.pushController(CatalogueSearchController(query).withFadeTransaction()) router.pushController(CatalogueSearchController(query).withFadeTransaction())
} }

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.ui.manga.track package eu.kanade.tachiyomi.ui.manga.track
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.view.LayoutInflater 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.data.track.model.TrackSearch
import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.base.controller.NucleusController
import eu.kanade.tachiyomi.ui.manga.MangaController 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.system.toast
import eu.kanade.tachiyomi.util.view.RecyclerWindowInsetsListener 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 kotlinx.android.synthetic.main.track_controller.*
import timber.log.Timber import timber.log.Timber
@ -41,13 +46,31 @@ class TrackController : NucleusController<TrackPresenter>(),
override fun onViewCreated(view: View) { override fun onViewCreated(view: View) {
super.onViewCreated(view) super.onViewCreated(view)
if ((parentController as MangaController).isLockedFromSearch) {
swipe_refresh.invisible()
unlock_button.visible()
unlock_button.setOnClickListener {
SecureActivityDelegate.promptLockIfNeeded(activity)
}
}
adapter = TrackAdapter(this) adapter = TrackAdapter(this)
with(view) { track_recycler.layoutManager = LinearLayoutManager(view.context)
track_recycler.layoutManager = LinearLayoutManager(context) track_recycler.adapter = adapter
track_recycler.adapter = adapter track_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener)
track_recycler.setOnApplyWindowInsetsListener(RecyclerWindowInsetsListener) swipe_refresh.isEnabled = false
swipe_refresh.isEnabled = false swipe_refresh.refreshes().subscribeUntilDestroy { presenter.refresh() }
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()
} }
} }

View File

@ -21,7 +21,6 @@ import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.SeekBar import android.widget.SeekBar
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.biometric.BiometricManager
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.kanade.tachiyomi.R 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.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity 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.AddToLibraryFirst
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success 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.R2LPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer 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.lang.plusAssign
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.getResourceColor 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.system.toast
import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
@ -67,7 +65,6 @@ import rx.subscriptions.CompositeSubscription
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.math.abs import kotlin.math.abs
@ -527,23 +524,6 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>(),
presenter.shareImage(page) 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 * Called from the presenter when a page is ready to be shared. It shows Android's default
* sharing tool. * sharing tool.

View File

@ -1,16 +1,15 @@
package eu.kanade.tachiyomi.ui.main package eu.kanade.tachiyomi.ui.security
import android.os.Bundle import android.os.Bundle
import androidx.biometric.BiometricPrompt import androidx.biometric.BiometricPrompt
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import uy.kohesive.injekt.injectLazy
import java.util.Date import java.util.Date
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
class BiometricActivity : BaseActivity() { class BiometricActivity : BaseActivity() {
val executor = Executors.newSingleThreadExecutor() private val executor: ExecutorService = Executors.newSingleThreadExecutor()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -24,15 +23,10 @@ class BiometricActivity : BaseActivity() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result) super.onAuthenticationSucceeded(result)
MainActivity.unlocked = true SecureActivityDelegate.locked = false
preferences.lastUnlock().set(Date().time) preferences.lastUnlock().set(Date().time)
finish() finish()
} }
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
// TODO("Called when a biometric is valid but not recognized.")
}
}) })
val promptInfo = BiometricPrompt.PromptInfo.Builder() val promptInfo = BiometricPrompt.PromptInfo.Builder()

View File

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

View File

@ -1,17 +1,14 @@
package eu.kanade.tachiyomi.ui.setting package eu.kanade.tachiyomi.ui.setting
import androidx.appcompat.app.AppCompatDelegate
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.updater.UpdaterJob 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.util.system.LocaleHelper
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.widget.preference.IntListMatPreference
import kotlinx.android.synthetic.main.main_activity.*
import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys
class SettingsGeneralController : SettingsController() { class SettingsGeneralController : SettingsController() {
@ -97,35 +94,53 @@ class SettingsGeneralController : SettingsController() {
} }
} }
val biometricManager = BiometricManager.from(context) preferenceCategory {
if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) { titleRes = R.string.pref_category_security
var preference:IntListMatPreference? = null
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 { switchPreference {
key = Keys.useBiometrics key = Keys.secureScreen
titleRes = R.string.lock_with_biometrics titleRes = R.string.pref_secure_screen
summaryRes = R.string.pref_secure_screen_summary
defaultValue = false defaultValue = false
onChange { onChange {
preference?.isVisible = it as Boolean it as Boolean
SecureActivityDelegate.setSecure(activity, it)
true 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
}
} }
} }
} }

View File

@ -185,7 +185,8 @@ class SettingsLibraryController : SettingsController() {
intListPreference(activity) { intListPreference(activity) {
titleRes = R.string.pref_keep_category_sorting titleRes = R.string.pref_keep_category_sorting
key = Keys.keepCatSort key = Keys.keepCatSort
summaryRes = R.string.pref_keep_category_sorting_summary
customSummary = context.getString(R.string.pref_keep_category_sorting_summary)
entries = listOf( entries = listOf(
context.getString(R.string.always_ask), context.getString(R.string.always_ask),
context.getString(R.string.option_keep_category_sort), context.getString(R.string.option_keep_category_sort),

View File

@ -27,7 +27,8 @@ AttributeSet? =
defValue = defaultValue as? Int ?: defValue defValue = defaultValue as? Int ?: defValue
} }
override fun getSummary(): CharSequence { 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()) val index = entryValues.indexOf(prefs.getInt(key, defValue).getOrDefault())
return if (entries.isEmpty() || index == -1) "" return if (entries.isEmpty() || index == -1) ""
else entries[index] else entries[index]

View File

@ -3,17 +3,11 @@ package eu.kanade.tachiyomi.widget.preference
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.util.AttributeSet import android.util.AttributeSet
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.afollestad.materialdialogs.list.listItemsSingleChoice
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault 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, open class ListMatPreference @JvmOverloads constructor(activity: Activity?, context: Context,
attrs: AttributeSet? = attrs: AttributeSet? =
@ -34,6 +28,7 @@ open class ListMatPreference @JvmOverloads constructor(activity: Activity?, cont
defValue = defaultValue as? String ?: defValue defValue = defaultValue as? String ?: defValue
} }
override fun getSummary(): CharSequence { override fun getSummary(): CharSequence {
if (customSummary != null) return customSummary!!
val index = entryValues.indexOf(prefs.getStringPref(key, defValue).getOrDefault()) val index = entryValues.indexOf(prefs.getStringPref(key, defValue).getOrDefault())
return if (entries.isEmpty() || index == -1) "" return if (entries.isEmpty() || index == -1) ""
else entries[index] else entries[index]

View File

@ -16,19 +16,10 @@ open class MatPreference @JvmOverloads constructor(val activity: Activity?, cont
null) : null) :
Preference(context, attrs) { Preference(context, attrs) {
protected var useCustomSummary = false
protected val prefs: PreferencesHelper = Injekt.get() protected val prefs: PreferencesHelper = Injekt.get()
private var isShowing = false 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() { override fun onClick() {
if (!isShowing) if (!isShowing)
dialog().apply { dialog().apply {
@ -37,6 +28,10 @@ open class MatPreference @JvmOverloads constructor(val activity: Activity?, cont
isShowing = true isShowing = true
} }
override fun getSummary(): CharSequence {
return customSummary ?: super.getSummary()
}
open fun dialog(): MaterialDialog { open fun dialog(): MaterialDialog {
return MaterialDialog(activity ?: context).apply { return MaterialDialog(activity ?: context).apply {
if (title != null) if (title != null)

View File

@ -20,10 +20,15 @@ class MultiListMatPreference @JvmOverloads constructor(activity: Activity?, cont
var customSummaryRes:Int var customSummaryRes:Int
get() = 0 get() = 0
set(value) { customSummary = context.getString(value) } set(value) { customSummary = context.getString(value) }
var customSummary:String? = null
override fun getSummary(): CharSequence { 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") @SuppressLint("CheckResult")

View File

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

View File

@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" 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 <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh" android:id="@+id/swipe_refresh"
@ -20,4 +22,17 @@
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </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>

View File

@ -48,6 +48,7 @@
<string name="hiding_categories">Hiding categories</string> <string name="hiding_categories">Hiding categories</string>
<string name="manga_only">Manga only</string> <string name="manga_only">Manga only</string>
<string name="manwha_only">Manwha 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="sorting_by_">Sorting by %1$s</string>
<string name="sort_by_">Sort 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_date_format">Date format</string>
<string name="pref_enable_automatic_updates">Check for updates</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_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 --> <!-- Library section -->
<string name="pref_category_library_display">Display</string> <string name="pref_category_library_display">Display</string>