|
@@ -6,33 +6,44 @@ import android.content.Intent
|
|
|
import android.graphics.Color
|
|
|
import android.os.Build
|
|
|
import android.os.Bundle
|
|
|
-import android.view.ViewGroup
|
|
|
+import android.view.View
|
|
|
import android.view.Window
|
|
|
import android.widget.Toast
|
|
|
-import androidx.appcompat.view.ActionMode
|
|
|
+import androidx.activity.compose.BackHandler
|
|
|
+import androidx.compose.material3.AlertDialog
|
|
|
+import androidx.compose.material3.Text
|
|
|
+import androidx.compose.material3.TextButton
|
|
|
+import androidx.compose.runtime.Composable
|
|
|
+import androidx.compose.runtime.LaunchedEffect
|
|
|
+import androidx.compose.runtime.getValue
|
|
|
+import androidx.compose.runtime.mutableStateOf
|
|
|
+import androidx.compose.runtime.remember
|
|
|
+import androidx.compose.runtime.rememberCoroutineScope
|
|
|
+import androidx.compose.runtime.setValue
|
|
|
+import androidx.compose.ui.platform.LocalContext
|
|
|
+import androidx.compose.ui.res.stringResource
|
|
|
import androidx.core.animation.doOnEnd
|
|
|
-import androidx.core.graphics.ColorUtils
|
|
|
import androidx.core.splashscreen.SplashScreen
|
|
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
|
import androidx.core.view.ViewCompat
|
|
|
import androidx.core.view.WindowCompat
|
|
|
import androidx.core.view.WindowInsetsCompat
|
|
|
-import androidx.core.view.isVisible
|
|
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
|
|
import androidx.interpolator.view.animation.LinearOutSlowInInterpolator
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
-import com.bluelinelabs.conductor.Conductor
|
|
|
-import com.bluelinelabs.conductor.Controller
|
|
|
-import com.bluelinelabs.conductor.ControllerChangeHandler
|
|
|
-import com.bluelinelabs.conductor.Router
|
|
|
-import com.bluelinelabs.conductor.RouterTransaction
|
|
|
-import com.google.android.material.navigation.NavigationBarView
|
|
|
+import cafe.adriel.voyager.navigator.LocalNavigator
|
|
|
+import cafe.adriel.voyager.navigator.Navigator
|
|
|
+import cafe.adriel.voyager.navigator.NavigatorDisposeBehavior
|
|
|
+import cafe.adriel.voyager.navigator.currentOrThrow
|
|
|
+import cafe.adriel.voyager.transitions.ScreenTransition
|
|
|
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
|
|
|
-import dev.chrisbanes.insetter.applyInsetter
|
|
|
import eu.kanade.domain.base.BasePreferences
|
|
|
+import eu.kanade.domain.category.model.Category
|
|
|
import eu.kanade.domain.library.service.LibraryPreferences
|
|
|
import eu.kanade.domain.source.service.SourcePreferences
|
|
|
import eu.kanade.domain.ui.UiPreferences
|
|
|
+import eu.kanade.presentation.util.Transition
|
|
|
+import eu.kanade.presentation.util.collectAsState
|
|
|
import eu.kanade.tachiyomi.BuildConfig
|
|
|
import eu.kanade.tachiyomi.Migrations
|
|
|
import eu.kanade.tachiyomi.R
|
|
@@ -40,39 +51,29 @@ import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
|
|
import eu.kanade.tachiyomi.data.updater.AppUpdateChecker
|
|
|
import eu.kanade.tachiyomi.data.updater.AppUpdateResult
|
|
|
-import eu.kanade.tachiyomi.databinding.MainActivityBinding
|
|
|
-import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
|
|
+import eu.kanade.tachiyomi.data.updater.RELEASE_URL
|
|
|
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
|
|
-import eu.kanade.tachiyomi.ui.base.controller.ComposeContentController
|
|
|
-import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
|
-import eu.kanade.tachiyomi.ui.base.controller.RootController
|
|
|
-import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
|
-import eu.kanade.tachiyomi.ui.base.controller.setRoot
|
|
|
-import eu.kanade.tachiyomi.ui.browse.BrowseController
|
|
|
-import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceController
|
|
|
-import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
|
|
|
-import eu.kanade.tachiyomi.ui.download.DownloadController
|
|
|
-import eu.kanade.tachiyomi.ui.history.HistoryController
|
|
|
-import eu.kanade.tachiyomi.ui.library.LibraryController
|
|
|
-import eu.kanade.tachiyomi.ui.manga.MangaController
|
|
|
-import eu.kanade.tachiyomi.ui.more.MoreController
|
|
|
-import eu.kanade.tachiyomi.ui.more.NewUpdateDialogController
|
|
|
-import eu.kanade.tachiyomi.ui.setting.SettingsMainController
|
|
|
-import eu.kanade.tachiyomi.ui.updates.UpdatesController
|
|
|
-import eu.kanade.tachiyomi.util.lang.launchIO
|
|
|
-import eu.kanade.tachiyomi.util.lang.launchUI
|
|
|
-import eu.kanade.tachiyomi.util.preference.asHotFlow
|
|
|
+import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
|
|
|
+import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
|
|
|
+import eu.kanade.tachiyomi.ui.home.HomeScreen
|
|
|
+import eu.kanade.tachiyomi.ui.library.LibrarySettingsSheet
|
|
|
+import eu.kanade.tachiyomi.ui.library.LibraryTab
|
|
|
+import eu.kanade.tachiyomi.ui.manga.MangaScreen
|
|
|
+import eu.kanade.tachiyomi.ui.more.NewUpdateScreen
|
|
|
+import eu.kanade.tachiyomi.util.Constants
|
|
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
|
|
-import eu.kanade.tachiyomi.util.system.getThemeColor
|
|
|
-import eu.kanade.tachiyomi.util.system.isTabletUi
|
|
|
import eu.kanade.tachiyomi.util.system.logcat
|
|
|
+import eu.kanade.tachiyomi.util.system.openInBrowser
|
|
|
import eu.kanade.tachiyomi.util.system.toast
|
|
|
+import eu.kanade.tachiyomi.util.view.setComposeContent
|
|
|
import eu.kanade.tachiyomi.util.view.setNavigationBarTransparentCompat
|
|
|
+import kotlinx.coroutines.cancel
|
|
|
import kotlinx.coroutines.delay
|
|
|
import kotlinx.coroutines.flow.drop
|
|
|
import kotlinx.coroutines.flow.launchIn
|
|
|
-import kotlinx.coroutines.flow.merge
|
|
|
import kotlinx.coroutines.flow.onEach
|
|
|
+import kotlinx.coroutines.launch
|
|
|
+import kotlinx.coroutines.runBlocking
|
|
|
import logcat.LogPriority
|
|
|
import uy.kohesive.injekt.Injekt
|
|
|
import uy.kohesive.injekt.api.get
|
|
@@ -86,24 +87,20 @@ class MainActivity : BaseActivity() {
|
|
|
private val uiPreferences: UiPreferences by injectLazy()
|
|
|
private val preferences: BasePreferences by injectLazy()
|
|
|
|
|
|
- lateinit var binding: MainActivityBinding
|
|
|
-
|
|
|
- private lateinit var router: Router
|
|
|
-
|
|
|
- private val startScreenId = R.id.nav_library
|
|
|
- private var isConfirmingExit: Boolean = false
|
|
|
private var isHandlingShortcut: Boolean = false
|
|
|
|
|
|
- /**
|
|
|
- * App bar lift state for backstack
|
|
|
- */
|
|
|
- private val backstackLiftState = mutableMapOf<String, Boolean>()
|
|
|
-
|
|
|
private val chapterCache: ChapterCache by injectLazy()
|
|
|
|
|
|
// To be checked by splash screen. If true then splash screen will be removed.
|
|
|
var ready = false
|
|
|
|
|
|
+ /**
|
|
|
+ * Sheet containing filter/sort/display items.
|
|
|
+ */
|
|
|
+ private var settingsSheet: LibrarySettingsSheet? = null
|
|
|
+
|
|
|
+ private lateinit var navigator: Navigator
|
|
|
+
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
// Prevent splash screen showing up on configuration changes
|
|
|
val splashScreen = if (savedInstanceState == null) installSplashScreen() else null
|
|
@@ -132,154 +129,138 @@ class MainActivity : BaseActivity() {
|
|
|
false
|
|
|
}
|
|
|
|
|
|
- binding = MainActivityBinding.inflate(layoutInflater)
|
|
|
-
|
|
|
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
|
|
if (!isTaskRoot) {
|
|
|
finish()
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- setContentView(binding.root)
|
|
|
- setSupportActionBar(binding.toolbar)
|
|
|
-
|
|
|
// Draw edge-to-edge
|
|
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
|
- binding.bottomNav?.applyInsetter {
|
|
|
- type(navigationBars = true) {
|
|
|
- padding()
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- val startTime = System.currentTimeMillis()
|
|
|
- splashScreen?.setKeepVisibleCondition {
|
|
|
- val elapsed = System.currentTimeMillis() - startTime
|
|
|
- elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION)
|
|
|
- }
|
|
|
- setSplashScreenExitAnimation(splashScreen)
|
|
|
+ settingsSheet = LibrarySettingsSheet(this)
|
|
|
+ LibraryTab.openSettingsSheetEvent
|
|
|
+ .onEach(::showSettingsSheet)
|
|
|
+ .launchIn(lifecycleScope)
|
|
|
|
|
|
- nav.setOnItemSelectedListener { item ->
|
|
|
- val id = item.itemId
|
|
|
-
|
|
|
- val currentRoot = router.backstack.firstOrNull()
|
|
|
- if (currentRoot?.tag()?.toIntOrNull() != id) {
|
|
|
- when (id) {
|
|
|
- R.id.nav_library -> router.setRoot(LibraryController(), id)
|
|
|
- R.id.nav_updates -> router.setRoot(UpdatesController(), id)
|
|
|
- R.id.nav_history -> router.setRoot(HistoryController(), id)
|
|
|
- R.id.nav_browse -> router.setRoot(BrowseController(toExtensions = false), id)
|
|
|
- R.id.nav_more -> router.setRoot(MoreController(), id)
|
|
|
+ setComposeContent {
|
|
|
+ Navigator(
|
|
|
+ screen = HomeScreen,
|
|
|
+ disposeBehavior = NavigatorDisposeBehavior(disposeNestedNavigators = false, disposeSteps = true),
|
|
|
+ ) { navigator ->
|
|
|
+ if (navigator.size == 1) {
|
|
|
+ ConfirmExit()
|
|
|
}
|
|
|
- } else if (!isHandlingShortcut) {
|
|
|
- when (id) {
|
|
|
- R.id.nav_library -> {
|
|
|
- val controller = router.getControllerWithTag(id.toString()) as? LibraryController
|
|
|
- controller?.showSettingsSheet()
|
|
|
- }
|
|
|
- R.id.nav_updates -> {
|
|
|
- if (router.backstackSize == 1) {
|
|
|
- router.pushController(DownloadController())
|
|
|
- }
|
|
|
- }
|
|
|
- R.id.nav_history -> {
|
|
|
- if (router.backstackSize == 1) {
|
|
|
- try {
|
|
|
- val historyController = router.backstack[0].controller as HistoryController
|
|
|
- historyController.resumeLastChapterRead()
|
|
|
- } catch (e: Exception) {
|
|
|
- toast(R.string.cant_open_last_read_chapter)
|
|
|
+
|
|
|
+ // Shows current screen
|
|
|
+ ScreenTransition(navigator = navigator, transition = { Transition.OneWayFade })
|
|
|
+
|
|
|
+ // Pop source-related screens when incognito mode is turned off
|
|
|
+ LaunchedEffect(Unit) {
|
|
|
+ preferences.incognitoMode().changes()
|
|
|
+ .drop(1)
|
|
|
+ .onEach {
|
|
|
+ if (!it) {
|
|
|
+ val currentScreen = navigator.lastItem
|
|
|
+ if (currentScreen is BrowseSourceScreen ||
|
|
|
+ (currentScreen is MangaScreen && currentScreen.fromSource)
|
|
|
+ ) {
|
|
|
+ navigator.popUntilRoot()
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- R.id.nav_more -> {
|
|
|
- if (router.backstackSize == 1) {
|
|
|
- router.pushController(SettingsMainController())
|
|
|
- }
|
|
|
- }
|
|
|
+ .launchIn(this)
|
|
|
}
|
|
|
- }
|
|
|
- true
|
|
|
- }
|
|
|
|
|
|
- val container: ViewGroup = binding.controllerContainer
|
|
|
- router = Conductor.attachRouter(this, container, savedInstanceState)
|
|
|
- .setPopRootControllerMode(Router.PopRootControllerMode.NEVER)
|
|
|
- router.addChangeListener(
|
|
|
- object : ControllerChangeHandler.ControllerChangeListener {
|
|
|
- override fun onChangeStarted(
|
|
|
- to: Controller?,
|
|
|
- from: Controller?,
|
|
|
- isPush: Boolean,
|
|
|
- container: ViewGroup,
|
|
|
- handler: ControllerChangeHandler,
|
|
|
- ) {
|
|
|
- syncActivityViewWithController(to, from, isPush)
|
|
|
+ LaunchedEffect(navigator) {
|
|
|
+ [email protected] = navigator
|
|
|
}
|
|
|
|
|
|
- override fun onChangeCompleted(
|
|
|
- to: Controller?,
|
|
|
- from: Controller?,
|
|
|
- isPush: Boolean,
|
|
|
- container: ViewGroup,
|
|
|
- handler: ControllerChangeHandler,
|
|
|
- ) {
|
|
|
- }
|
|
|
- },
|
|
|
- )
|
|
|
- if (!router.hasRootController()) {
|
|
|
- // Set start screen
|
|
|
- if (!handleIntentAction(intent)) {
|
|
|
- moveToStartScreen()
|
|
|
+ CheckForUpdate()
|
|
|
+ }
|
|
|
+
|
|
|
+ var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
|
|
|
+ if (showChangelog) {
|
|
|
+ AlertDialog(
|
|
|
+ onDismissRequest = { showChangelog = false },
|
|
|
+ title = { Text(text = stringResource(R.string.updated_version, BuildConfig.VERSION_NAME)) },
|
|
|
+ dismissButton = {
|
|
|
+ TextButton(onClick = { openInBrowser(RELEASE_URL) }) {
|
|
|
+ Text(text = stringResource(R.string.whats_new))
|
|
|
+ }
|
|
|
+ },
|
|
|
+ confirmButton = {
|
|
|
+ TextButton(onClick = { showChangelog = false }) {
|
|
|
+ Text(text = stringResource(android.R.string.ok))
|
|
|
+ }
|
|
|
+ },
|
|
|
+ )
|
|
|
}
|
|
|
}
|
|
|
- syncActivityViewWithController()
|
|
|
|
|
|
- binding.toolbar.setNavigationOnClickListener {
|
|
|
- onBackPressed()
|
|
|
+ val startTime = System.currentTimeMillis()
|
|
|
+ splashScreen?.setKeepVisibleCondition {
|
|
|
+ val elapsed = System.currentTimeMillis() - startTime
|
|
|
+ elapsed <= SPLASH_MIN_DURATION || (!ready && elapsed <= SPLASH_MAX_DURATION)
|
|
|
}
|
|
|
+ setSplashScreenExitAnimation(splashScreen)
|
|
|
|
|
|
if (savedInstanceState == null) {
|
|
|
+ // Set start screen
|
|
|
+ lifecycleScope.launch { handleIntentAction(intent) }
|
|
|
+
|
|
|
// Reset Incognito Mode on relaunch
|
|
|
preferences.incognitoMode().set(false)
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // Show changelog prompt on update
|
|
|
- if (didMigration && !BuildConfig.DEBUG) {
|
|
|
- WhatsNewDialogController().showDialog(router)
|
|
|
- }
|
|
|
+ private fun showSettingsSheet(category: Category? = null) {
|
|
|
+ if (category != null) {
|
|
|
+ settingsSheet?.show(category)
|
|
|
} else {
|
|
|
- // Restore selected nav item
|
|
|
- router.backstack.firstOrNull()?.tag()?.toIntOrNull()?.let {
|
|
|
- nav.menu.findItem(it).isChecked = true
|
|
|
- }
|
|
|
+ lifecycleScope.launch { LibraryTab.requestOpenSettingsSheet() }
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- merge(libraryPreferences.showUpdatesNavBadge().changes(), libraryPreferences.unreadUpdatesCount().changes())
|
|
|
- .onEach { setUnreadUpdatesBadge() }
|
|
|
- .launchIn(lifecycleScope)
|
|
|
-
|
|
|
- sourcePreferences.extensionUpdatesCount()
|
|
|
- .asHotFlow { setExtensionsBadge() }
|
|
|
- .launchIn(lifecycleScope)
|
|
|
-
|
|
|
- preferences.downloadedOnly()
|
|
|
- .asHotFlow { binding.downloadedOnly.isVisible = it }
|
|
|
- .launchIn(lifecycleScope)
|
|
|
+ @Composable
|
|
|
+ private fun ConfirmExit() {
|
|
|
+ val scope = rememberCoroutineScope()
|
|
|
+ val confirmExit by preferences.confirmExit().collectAsState()
|
|
|
+ var waitingConfirmation by remember { mutableStateOf(false) }
|
|
|
+ BackHandler(enabled = !waitingConfirmation && confirmExit) {
|
|
|
+ scope.launch {
|
|
|
+ waitingConfirmation = true
|
|
|
+ val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG)
|
|
|
+ delay(2.seconds)
|
|
|
+ toast.cancel()
|
|
|
+ waitingConfirmation = false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- binding.incognitoMode.isVisible = preferences.incognitoMode().get()
|
|
|
- preferences.incognitoMode().changes()
|
|
|
- .drop(1)
|
|
|
- .onEach {
|
|
|
- binding.incognitoMode.isVisible = it
|
|
|
-
|
|
|
- // Close BrowseSourceController and its MangaController child when incognito mode is disabled
|
|
|
- if (!it) {
|
|
|
- val fg = router.backstack.lastOrNull()?.controller
|
|
|
- if (fg is BrowseSourceController || fg is MangaController && fg.fromSource) {
|
|
|
- router.popToRoot()
|
|
|
+ @Composable
|
|
|
+ private fun CheckForUpdate() {
|
|
|
+ val context = LocalContext.current
|
|
|
+ val navigator = LocalNavigator.currentOrThrow
|
|
|
+ LaunchedEffect(Unit) {
|
|
|
+ // App updates
|
|
|
+ if (BuildConfig.INCLUDE_UPDATER) {
|
|
|
+ try {
|
|
|
+ val result = AppUpdateChecker().checkForUpdate(context)
|
|
|
+ if (result is AppUpdateResult.NewUpdate) {
|
|
|
+ val updateScreen = NewUpdateScreen(
|
|
|
+ versionName = result.release.version,
|
|
|
+ changelogInfo = result.release.info,
|
|
|
+ releaseLink = result.release.releaseLink,
|
|
|
+ downloadLink = result.release.getDownloadLink(),
|
|
|
+ )
|
|
|
+ navigator.push(updateScreen)
|
|
|
}
|
|
|
+ } catch (e: Exception) {
|
|
|
+ logcat(LogPriority.ERROR, e)
|
|
|
}
|
|
|
}
|
|
|
- .launchIn(lifecycleScope)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -289,16 +270,16 @@ class MainActivity : BaseActivity() {
|
|
|
* after the animation is finished.
|
|
|
*/
|
|
|
private fun setSplashScreenExitAnimation(splashScreen: SplashScreen?) {
|
|
|
+ val root = findViewById<View>(android.R.id.content)
|
|
|
val setNavbarScrim = {
|
|
|
// Make sure navigation bar is on bottom before we modify it
|
|
|
- ViewCompat.setOnApplyWindowInsetsListener(binding.root) { _, insets ->
|
|
|
+ ViewCompat.setOnApplyWindowInsetsListener(root) { _, insets ->
|
|
|
if (insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom > 0) {
|
|
|
- val elevation = binding.bottomNav?.elevation ?: 0F
|
|
|
- window.setNavigationBarTransparentCompat(this@MainActivity, elevation)
|
|
|
+ window.setNavigationBarTransparentCompat(this@MainActivity, 3.dpToPx.toFloat())
|
|
|
}
|
|
|
insets
|
|
|
}
|
|
|
- ViewCompat.requestApplyInsets(binding.root)
|
|
|
+ ViewCompat.requestApplyInsets(root)
|
|
|
}
|
|
|
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S && splashScreen != null) {
|
|
@@ -316,7 +297,7 @@ class MainActivity : BaseActivity() {
|
|
|
duration = SPLASH_EXIT_ANIM_DURATION
|
|
|
addUpdateListener { va ->
|
|
|
val value = va.animatedValue as Float
|
|
|
- binding.root.translationY = value * 16.dpToPx
|
|
|
+ root.translationY = value * 16.dpToPx
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -344,69 +325,13 @@ class MainActivity : BaseActivity() {
|
|
|
}
|
|
|
|
|
|
override fun onNewIntent(intent: Intent) {
|
|
|
- if (!handleIntentAction(intent)) {
|
|
|
+ val handle = runBlocking { handleIntentAction(intent) }
|
|
|
+ if (!handle) {
|
|
|
super.onNewIntent(intent)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- override fun onResume() {
|
|
|
- super.onResume()
|
|
|
- checkForUpdates()
|
|
|
- }
|
|
|
-
|
|
|
- private fun checkForUpdates() {
|
|
|
- lifecycleScope.launchIO {
|
|
|
- // App updates
|
|
|
- if (BuildConfig.INCLUDE_UPDATER) {
|
|
|
- try {
|
|
|
- val result = AppUpdateChecker().checkForUpdate(this@MainActivity)
|
|
|
- if (result is AppUpdateResult.NewUpdate) {
|
|
|
- NewUpdateDialogController(result).showDialog(router)
|
|
|
- }
|
|
|
- } catch (e: Exception) {
|
|
|
- logcat(LogPriority.ERROR, e)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Extension updates
|
|
|
- try {
|
|
|
- ExtensionGithubApi().checkForUpdates(
|
|
|
- this@MainActivity,
|
|
|
- fromAvailableExtensionList = true,
|
|
|
- )?.let { pendingUpdates ->
|
|
|
- sourcePreferences.extensionUpdatesCount().set(pendingUpdates.size)
|
|
|
- }
|
|
|
- } catch (e: Exception) {
|
|
|
- logcat(LogPriority.ERROR, e)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun setUnreadUpdatesBadge() {
|
|
|
- val updates = if (libraryPreferences.showUpdatesNavBadge().get()) libraryPreferences.unreadUpdatesCount().get() else 0
|
|
|
- if (updates > 0) {
|
|
|
- nav.getOrCreateBadge(R.id.nav_updates).apply {
|
|
|
- number = updates
|
|
|
- setContentDescriptionQuantityStringsResource(R.plurals.notification_chapters_generic)
|
|
|
- }
|
|
|
- } else {
|
|
|
- nav.removeBadge(R.id.nav_updates)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun setExtensionsBadge() {
|
|
|
- val updates = sourcePreferences.extensionUpdatesCount().get()
|
|
|
- if (updates > 0) {
|
|
|
- nav.getOrCreateBadge(R.id.nav_browse).apply {
|
|
|
- number = updates
|
|
|
- setContentDescriptionQuantityStringsResource(R.plurals.update_check_notification_ext_updates)
|
|
|
- }
|
|
|
- } else {
|
|
|
- nav.removeBadge(R.id.nav_browse)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun handleIntentAction(intent: Intent): Boolean {
|
|
|
+ private suspend fun handleIntentAction(intent: Intent): Boolean {
|
|
|
val notificationId = intent.getIntExtra("notificationId", -1)
|
|
|
if (notificationId > -1) {
|
|
|
NotificationReceiver.dismissNotification(applicationContext, notificationId, intent.getIntExtra("groupId", 0))
|
|
@@ -415,32 +340,19 @@ class MainActivity : BaseActivity() {
|
|
|
isHandlingShortcut = true
|
|
|
|
|
|
when (intent.action) {
|
|
|
- SHORTCUT_LIBRARY -> setSelectedNavItem(R.id.nav_library)
|
|
|
- SHORTCUT_RECENTLY_UPDATED -> setSelectedNavItem(R.id.nav_updates)
|
|
|
- SHORTCUT_RECENTLY_READ -> setSelectedNavItem(R.id.nav_history)
|
|
|
- SHORTCUT_CATALOGUES -> setSelectedNavItem(R.id.nav_browse)
|
|
|
- SHORTCUT_EXTENSIONS -> {
|
|
|
- if (router.backstackSize > 1) {
|
|
|
- router.popToRoot()
|
|
|
- }
|
|
|
- setSelectedNavItem(R.id.nav_browse)
|
|
|
- router.pushController(BrowseController(toExtensions = true))
|
|
|
- }
|
|
|
+ SHORTCUT_LIBRARY -> HomeScreen.openTab(HomeScreen.Tab.Library())
|
|
|
+ SHORTCUT_RECENTLY_UPDATED -> HomeScreen.openTab(HomeScreen.Tab.Updates)
|
|
|
+ SHORTCUT_RECENTLY_READ -> HomeScreen.openTab(HomeScreen.Tab.History)
|
|
|
+ SHORTCUT_CATALOGUES -> HomeScreen.openTab(HomeScreen.Tab.Browse(false))
|
|
|
+ SHORTCUT_EXTENSIONS -> HomeScreen.openTab(HomeScreen.Tab.Browse(true))
|
|
|
SHORTCUT_MANGA -> {
|
|
|
- val extras = intent.extras ?: return false
|
|
|
- val fgController = router.backstack.lastOrNull()?.controller as? MangaController
|
|
|
- if (fgController?.mangaId != extras.getLong(MangaController.MANGA_EXTRA)) {
|
|
|
- router.popToRoot()
|
|
|
- setSelectedNavItem(R.id.nav_library)
|
|
|
- router.pushController(RouterTransaction.with(MangaController(extras)))
|
|
|
- }
|
|
|
+ val idToOpen = intent.extras?.getLong(Constants.MANGA_EXTRA) ?: return false
|
|
|
+ navigator.popUntilRoot()
|
|
|
+ HomeScreen.openTab(HomeScreen.Tab.Library(idToOpen))
|
|
|
}
|
|
|
SHORTCUT_DOWNLOADS -> {
|
|
|
- if (router.backstackSize > 1) {
|
|
|
- router.popToRoot()
|
|
|
- }
|
|
|
- setSelectedNavItem(R.id.nav_more)
|
|
|
- router.pushController(DownloadController())
|
|
|
+ navigator.popUntilRoot()
|
|
|
+ HomeScreen.openTab(HomeScreen.Tab.More(toDownloads = true))
|
|
|
}
|
|
|
Intent.ACTION_SEARCH, Intent.ACTION_SEND, "com.google.android.gms.actions.SEARCH_ACTION" -> {
|
|
|
// If the intent match the "standard" Android search intent
|
|
@@ -449,20 +361,16 @@ class MainActivity : BaseActivity() {
|
|
|
// Get the search query provided in extras, and if not null, perform a global search with it.
|
|
|
val query = intent.getStringExtra(SearchManager.QUERY) ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
|
|
if (query != null && query.isNotEmpty()) {
|
|
|
- if (router.backstackSize > 1) {
|
|
|
- router.popToRoot()
|
|
|
- }
|
|
|
- router.pushController(GlobalSearchController(query))
|
|
|
+ navigator.popUntilRoot()
|
|
|
+ navigator.push(GlobalSearchScreen(query))
|
|
|
}
|
|
|
}
|
|
|
INTENT_SEARCH -> {
|
|
|
val query = intent.getStringExtra(INTENT_SEARCH_QUERY)
|
|
|
if (query != null && query.isNotEmpty()) {
|
|
|
- val filter = intent.getStringExtra(INTENT_SEARCH_FILTER)
|
|
|
- if (router.backstackSize > 1) {
|
|
|
- router.popToRoot()
|
|
|
- }
|
|
|
- router.pushController(GlobalSearchController(query, filter ?: ""))
|
|
|
+ val filter = intent.getStringExtra(INTENT_SEARCH_FILTER) ?: ""
|
|
|
+ navigator.popUntilRoot()
|
|
|
+ navigator.push(GlobalSearchScreen(query, filter))
|
|
|
}
|
|
|
}
|
|
|
else -> {
|
|
@@ -476,167 +384,22 @@ class MainActivity : BaseActivity() {
|
|
|
return true
|
|
|
}
|
|
|
|
|
|
- @Suppress("UNNECESSARY_SAFE_CALL")
|
|
|
override fun onDestroy() {
|
|
|
+ settingsSheet?.sheetScope?.cancel()
|
|
|
+ settingsSheet = null
|
|
|
super.onDestroy()
|
|
|
-
|
|
|
- // Binding sometimes isn't actually instantiated yet somehow
|
|
|
- nav?.setOnItemSelectedListener(null)
|
|
|
- binding?.toolbar?.setNavigationOnClickListener(null)
|
|
|
}
|
|
|
|
|
|
override fun onBackPressed() {
|
|
|
- if (router.handleBack()) {
|
|
|
- // A Router is consuming back press
|
|
|
- return
|
|
|
- }
|
|
|
- val backstackSize = router.backstackSize
|
|
|
- val startScreen = router.getControllerWithTag("$startScreenId")
|
|
|
- if (backstackSize == 1 && startScreen == null) {
|
|
|
- // Return to start screen
|
|
|
- moveToStartScreen()
|
|
|
- } else if (shouldHandleExitConfirmation()) {
|
|
|
- // Exit confirmation (resets after 2 seconds)
|
|
|
- lifecycleScope.launchUI { resetExitConfirmation() }
|
|
|
- } else if (backstackSize == 1) {
|
|
|
- // Regular back (i.e. closing the app)
|
|
|
- if (libraryPreferences.autoClearChapterCache().get()) {
|
|
|
- chapterCache.clear()
|
|
|
- }
|
|
|
- super.onBackPressed()
|
|
|
+ if (navigator.size == 1 &&
|
|
|
+ !onBackPressedDispatcher.hasEnabledCallbacks() &&
|
|
|
+ libraryPreferences.autoClearChapterCache().get()
|
|
|
+ ) {
|
|
|
+ chapterCache.clear()
|
|
|
}
|
|
|
+ super.onBackPressed()
|
|
|
}
|
|
|
|
|
|
- fun moveToStartScreen() {
|
|
|
- setSelectedNavItem(startScreenId)
|
|
|
- }
|
|
|
-
|
|
|
- override fun onSupportActionModeStarted(mode: ActionMode) {
|
|
|
- binding.appbar.apply {
|
|
|
- tag = isTransparentWhenNotLifted
|
|
|
- isTransparentWhenNotLifted = false
|
|
|
- }
|
|
|
- // Color taken from m3_appbar_background
|
|
|
- window.statusBarColor = ColorUtils.compositeColors(
|
|
|
- getColor(R.color.m3_appbar_overlay_color),
|
|
|
- getThemeColor(R.attr.colorSurface),
|
|
|
- )
|
|
|
- super.onSupportActionModeStarted(mode)
|
|
|
- }
|
|
|
-
|
|
|
- override fun onSupportActionModeFinished(mode: ActionMode) {
|
|
|
- binding.appbar.apply {
|
|
|
- isTransparentWhenNotLifted = (tag as? Boolean) ?: false
|
|
|
- tag = null
|
|
|
- }
|
|
|
- window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
|
|
|
- super.onSupportActionModeFinished(mode)
|
|
|
- }
|
|
|
-
|
|
|
- private suspend fun resetExitConfirmation() {
|
|
|
- isConfirmingExit = true
|
|
|
- val toast = toast(R.string.confirm_exit, Toast.LENGTH_LONG)
|
|
|
- delay(2.seconds)
|
|
|
- toast.cancel()
|
|
|
- isConfirmingExit = false
|
|
|
- }
|
|
|
-
|
|
|
- private fun shouldHandleExitConfirmation(): Boolean {
|
|
|
- return router.backstackSize == 1 &&
|
|
|
- router.getControllerWithTag("$startScreenId") != null &&
|
|
|
- preferences.confirmExit().get() &&
|
|
|
- !isConfirmingExit
|
|
|
- }
|
|
|
-
|
|
|
- fun setSelectedNavItem(itemId: Int) {
|
|
|
- if (!isFinishing) {
|
|
|
- nav.selectedItemId = itemId
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun syncActivityViewWithController(
|
|
|
- to: Controller? = null,
|
|
|
- from: Controller? = null,
|
|
|
- isPush: Boolean = true,
|
|
|
- ) {
|
|
|
- var internalTo = to
|
|
|
-
|
|
|
- if (internalTo == null) {
|
|
|
- // Should go here when the activity is recreated and dialog controller is on top of the backstack
|
|
|
- // Then we'll assume the top controller is the parent controller of this dialog
|
|
|
- val backstack = router.backstack
|
|
|
- internalTo = backstack.lastOrNull()?.controller
|
|
|
- if (internalTo is DialogController) {
|
|
|
- internalTo = backstack.getOrNull(backstack.size - 2)?.controller ?: return
|
|
|
- }
|
|
|
- } else {
|
|
|
- // Ignore changes for normal transactions
|
|
|
- if (from is DialogController || internalTo is DialogController) {
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- supportActionBar?.setDisplayHomeAsUpEnabled(router.backstackSize != 1)
|
|
|
-
|
|
|
- // Always show appbar again when changing controllers
|
|
|
- binding.appbar.setExpanded(true)
|
|
|
-
|
|
|
- if ((from == null || from is RootController) && internalTo !is RootController) {
|
|
|
- showNav(false)
|
|
|
- }
|
|
|
- if (internalTo is RootController) {
|
|
|
- // Always show bottom nav again when returning to a RootController
|
|
|
- showNav(true)
|
|
|
- }
|
|
|
-
|
|
|
- val isComposeController = internalTo is ComposeContentController
|
|
|
- binding.appbar.isVisible = !isComposeController
|
|
|
- binding.controllerContainer.enableScrollingBehavior(!isComposeController)
|
|
|
-
|
|
|
- if (!isTabletUi()) {
|
|
|
- // Save lift state
|
|
|
- if (isPush) {
|
|
|
- if (router.backstackSize > 1) {
|
|
|
- // Save lift state
|
|
|
- from?.let {
|
|
|
- backstackLiftState[it.instanceId] = binding.appbar.isLifted
|
|
|
- }
|
|
|
- } else {
|
|
|
- backstackLiftState.clear()
|
|
|
- }
|
|
|
- binding.appbar.isLifted = false
|
|
|
- } else {
|
|
|
- internalTo?.let {
|
|
|
- binding.appbar.isLifted = backstackLiftState.getOrElse(it.instanceId) { false }
|
|
|
- }
|
|
|
- from?.let {
|
|
|
- backstackLiftState.remove(it.instanceId)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun showNav(visible: Boolean) {
|
|
|
- showBottomNav(visible)
|
|
|
- showSideNav(visible)
|
|
|
- }
|
|
|
-
|
|
|
- // Also used from some controllers to swap bottom nav with action toolbar
|
|
|
- fun showBottomNav(visible: Boolean) {
|
|
|
- if (visible) {
|
|
|
- binding.bottomNav?.slideUp()
|
|
|
- } else {
|
|
|
- binding.bottomNav?.slideDown()
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private fun showSideNav(visible: Boolean) {
|
|
|
- binding.sideNav?.isVisible = visible
|
|
|
- }
|
|
|
-
|
|
|
- private val nav: NavigationBarView
|
|
|
- get() = binding.bottomNav ?: binding.sideNav!!
|
|
|
-
|
|
|
init {
|
|
|
registerSecureActivity(this)
|
|
|
}
|