|
|
|
|
@ -5,88 +5,93 @@
|
|
|
|
|
*/
|
|
|
|
|
package com.todoroo.astrid.activity
|
|
|
|
|
|
|
|
|
|
import android.app.ActivityOptions
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
import android.graphics.Color
|
|
|
|
|
import android.os.Bundle
|
|
|
|
|
import androidx.activity.SystemBarStyle
|
|
|
|
|
import androidx.activity.compose.BackHandler
|
|
|
|
|
import android.view.View
|
|
|
|
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
|
|
|
|
import androidx.activity.compose.setContent
|
|
|
|
|
import androidx.activity.enableEdgeToEdge
|
|
|
|
|
import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
|
import androidx.activity.viewModels
|
|
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
|
|
|
import androidx.appcompat.view.ActionMode
|
|
|
|
|
import androidx.compose.material3.DrawerValue
|
|
|
|
|
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
|
|
|
|
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
|
|
|
|
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
|
|
|
|
|
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
|
|
|
|
|
import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldRole
|
|
|
|
|
import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth
|
|
|
|
|
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
|
|
|
|
|
import androidx.compose.material3.rememberDrawerState
|
|
|
|
|
import androidx.compose.runtime.LaunchedEffect
|
|
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
|
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
|
|
import androidx.compose.material3.ModalBottomSheet
|
|
|
|
|
import androidx.compose.material3.rememberModalBottomSheetState
|
|
|
|
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
|
|
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
|
|
|
|
import androidx.compose.ui.platform.LocalContext
|
|
|
|
|
import androidx.core.content.IntentCompat.getParcelableExtra
|
|
|
|
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
|
|
|
import androidx.hilt.navigation.compose.hiltViewModel
|
|
|
|
|
import androidx.lifecycle.Lifecycle
|
|
|
|
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
|
|
|
import androidx.lifecycle.flowWithLifecycle
|
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
|
|
import androidx.navigation.compose.NavHost
|
|
|
|
|
import androidx.navigation.compose.composable
|
|
|
|
|
import androidx.navigation.compose.rememberNavController
|
|
|
|
|
import androidx.navigation.toRoute
|
|
|
|
|
import androidx.lifecycle.repeatOnLifecycle
|
|
|
|
|
import com.todoroo.andlib.utility.AndroidUtilities
|
|
|
|
|
import com.todoroo.andlib.utility.AndroidUtilities.atLeastR
|
|
|
|
|
import com.todoroo.astrid.activity.TaskEditFragment.Companion.newTaskEditFragment
|
|
|
|
|
import com.todoroo.astrid.adapter.SubheaderClickHandler
|
|
|
|
|
import com.todoroo.astrid.dao.TaskDao
|
|
|
|
|
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity
|
|
|
|
|
import com.todoroo.astrid.service.TaskCreator
|
|
|
|
|
import dagger.hilt.android.AndroidEntryPoint
|
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
|
import kotlinx.coroutines.async
|
|
|
|
|
import kotlinx.coroutines.coroutineScope
|
|
|
|
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
|
|
|
|
import kotlinx.coroutines.flow.launchIn
|
|
|
|
|
import kotlinx.coroutines.flow.map
|
|
|
|
|
import kotlinx.coroutines.flow.onEach
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
import kotlinx.coroutines.runBlocking
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
import org.tasks.BuildConfig
|
|
|
|
|
import org.tasks.R
|
|
|
|
|
import org.tasks.Tasks
|
|
|
|
|
import org.tasks.activities.GoogleTaskListSettingsActivity
|
|
|
|
|
import org.tasks.activities.TagSettingsActivity
|
|
|
|
|
import org.tasks.analytics.Firebase
|
|
|
|
|
import org.tasks.auth.SignInActivity
|
|
|
|
|
import org.tasks.billing.Inventory
|
|
|
|
|
import org.tasks.caldav.CaldavAccountSettingsActivity
|
|
|
|
|
import org.tasks.compose.AddAccountDestination
|
|
|
|
|
import org.tasks.compose.HomeDestination
|
|
|
|
|
import org.tasks.compose.accounts.AddAccountScreen
|
|
|
|
|
import org.tasks.compose.accounts.AddAccountViewModel
|
|
|
|
|
import org.tasks.compose.home.HomeScreen
|
|
|
|
|
import org.tasks.billing.PurchaseActivity
|
|
|
|
|
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
|
|
|
|
|
import org.tasks.compose.drawer.DrawerAction
|
|
|
|
|
import org.tasks.compose.drawer.DrawerItem
|
|
|
|
|
import org.tasks.compose.drawer.MenuSearchBar
|
|
|
|
|
import org.tasks.compose.drawer.TaskListDrawer
|
|
|
|
|
import org.tasks.data.dao.AlarmDao
|
|
|
|
|
import org.tasks.data.dao.CaldavDao
|
|
|
|
|
import org.tasks.data.dao.LocationDao
|
|
|
|
|
import org.tasks.data.dao.TagDataDao
|
|
|
|
|
import org.tasks.data.entity.Place
|
|
|
|
|
import org.tasks.data.entity.Task
|
|
|
|
|
import org.tasks.dialogs.ImportTasksDialog
|
|
|
|
|
import org.tasks.data.getLocation
|
|
|
|
|
import org.tasks.data.listSettingsClass
|
|
|
|
|
import org.tasks.databinding.TaskListActivityBinding
|
|
|
|
|
import org.tasks.dialogs.NewFilterDialog
|
|
|
|
|
import org.tasks.etebase.EtebaseAccountSettingsActivity
|
|
|
|
|
import org.tasks.dialogs.WhatsNewDialog
|
|
|
|
|
import org.tasks.extensions.Context.findActivity
|
|
|
|
|
import org.tasks.extensions.Context.nightMode
|
|
|
|
|
import org.tasks.extensions.Context.toast
|
|
|
|
|
import org.tasks.extensions.broughtToFront
|
|
|
|
|
import org.tasks.extensions.flagsToString
|
|
|
|
|
import org.tasks.extensions.isFromHistory
|
|
|
|
|
import org.tasks.files.FileHelper
|
|
|
|
|
import org.tasks.extensions.Context.openUri
|
|
|
|
|
import org.tasks.extensions.hideKeyboard
|
|
|
|
|
import org.tasks.filters.Filter
|
|
|
|
|
import org.tasks.jobs.WorkManager
|
|
|
|
|
import org.tasks.filters.FilterProvider
|
|
|
|
|
import org.tasks.filters.FilterProvider.Companion.REQUEST_NEW_LIST
|
|
|
|
|
import org.tasks.filters.FilterProvider.Companion.REQUEST_NEW_PLACE
|
|
|
|
|
import org.tasks.filters.FilterProvider.Companion.REQUEST_NEW_TAGS
|
|
|
|
|
import org.tasks.filters.NavigationDrawerSubheader
|
|
|
|
|
import org.tasks.filters.PlaceFilter
|
|
|
|
|
import org.tasks.location.LocationPickerActivity
|
|
|
|
|
import org.tasks.location.LocationPickerActivity.Companion.EXTRA_PLACE
|
|
|
|
|
import org.tasks.preferences.DefaultFilterProvider
|
|
|
|
|
import org.tasks.preferences.HelpAndFeedback
|
|
|
|
|
import org.tasks.preferences.MainPreferences
|
|
|
|
|
import org.tasks.preferences.Preferences
|
|
|
|
|
import org.tasks.preferences.fragments.FRAG_TAG_IMPORT_TASKS
|
|
|
|
|
import org.tasks.sync.AddAccountDialog
|
|
|
|
|
import org.tasks.sync.SyncAdapters
|
|
|
|
|
import org.tasks.sync.microsoft.MicrosoftSignInViewModel
|
|
|
|
|
import org.tasks.themes.ColorProvider
|
|
|
|
|
import org.tasks.themes.TasksTheme
|
|
|
|
|
import org.tasks.themes.Theme
|
|
|
|
|
import org.tasks.ui.EmptyTaskEditFragment.Companion.newEmptyTaskEditFragment
|
|
|
|
|
import org.tasks.ui.MainActivityEvent
|
|
|
|
|
import org.tasks.ui.MainActivityEventBus
|
|
|
|
|
import timber.log.Timber
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
|
|
|
|
@AndroidEntryPoint
|
|
|
|
|
class MainActivity : AppCompatActivity() {
|
|
|
|
|
@Inject lateinit var preferences: Preferences
|
|
|
|
|
@ -99,209 +104,286 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
@Inject lateinit var locationDao: LocationDao
|
|
|
|
|
@Inject lateinit var tagDataDao: TagDataDao
|
|
|
|
|
@Inject lateinit var alarmDao: AlarmDao
|
|
|
|
|
@Inject lateinit var eventBus: MainActivityEventBus
|
|
|
|
|
@Inject lateinit var firebase: Firebase
|
|
|
|
|
@Inject lateinit var caldavDao: CaldavDao
|
|
|
|
|
@Inject lateinit var syncAdapters: SyncAdapters
|
|
|
|
|
@Inject lateinit var workManager: WorkManager
|
|
|
|
|
|
|
|
|
|
private val viewModel: MainActivityViewModel by viewModels()
|
|
|
|
|
private var currentNightMode = 0
|
|
|
|
|
private var currentPro = false
|
|
|
|
|
private var actionMode: ActionMode? = null
|
|
|
|
|
private var isReady = false
|
|
|
|
|
private lateinit var binding: TaskListActivityBinding
|
|
|
|
|
|
|
|
|
|
/** @see android.app.Activity.onCreate
|
|
|
|
|
*/
|
|
|
|
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
|
|
super.onCreate(savedInstanceState)
|
|
|
|
|
theme.themeBase.set(this)
|
|
|
|
|
val splashScreen = installSplashScreen()
|
|
|
|
|
splashScreen.setKeepOnScreenCondition { !isReady }
|
|
|
|
|
theme.applyTheme(this)
|
|
|
|
|
currentNightMode = nightMode
|
|
|
|
|
currentPro = inventory.hasPro
|
|
|
|
|
binding = TaskListActivityBinding.inflate(layoutInflater)
|
|
|
|
|
setContentView(binding.root)
|
|
|
|
|
logIntent("onCreate")
|
|
|
|
|
handleIntent()
|
|
|
|
|
|
|
|
|
|
enableEdgeToEdge(
|
|
|
|
|
statusBarStyle = SystemBarStyle.auto(
|
|
|
|
|
lightScrim = Color.TRANSPARENT,
|
|
|
|
|
darkScrim = Color.TRANSPARENT
|
|
|
|
|
),
|
|
|
|
|
navigationBarStyle = if (theme.themeBase.isDarkTheme(this)) {
|
|
|
|
|
SystemBarStyle.dark(Color.TRANSPARENT)
|
|
|
|
|
} else {
|
|
|
|
|
SystemBarStyle.light(Color.TRANSPARENT, Color.TRANSPARENT)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
setContent {
|
|
|
|
|
TasksTheme(
|
|
|
|
|
theme = theme.themeBase.index,
|
|
|
|
|
primary = theme.themeColor.primaryColor,
|
|
|
|
|
) {
|
|
|
|
|
val navController = rememberNavController()
|
|
|
|
|
val hasAccount = viewModel
|
|
|
|
|
.accountExists
|
|
|
|
|
.collectAsStateWithLifecycle(null)
|
|
|
|
|
.value
|
|
|
|
|
LaunchedEffect(hasAccount) {
|
|
|
|
|
Timber.d("hasAccount=$hasAccount")
|
|
|
|
|
if (hasAccount == false) {
|
|
|
|
|
navController.navigate(AddAccountDestination(showImport = true))
|
|
|
|
|
}
|
|
|
|
|
isReady = hasAccount != null
|
|
|
|
|
}
|
|
|
|
|
NavHost(
|
|
|
|
|
navController = navController,
|
|
|
|
|
startDestination = HomeDestination,
|
|
|
|
|
) {
|
|
|
|
|
composable<AddAccountDestination> {
|
|
|
|
|
val route = it.toRoute<AddAccountDestination>()
|
|
|
|
|
LaunchedEffect(hasAccount) {
|
|
|
|
|
if (route.showImport && hasAccount == true) {
|
|
|
|
|
navController.popBackStack()
|
|
|
|
|
}
|
|
|
|
|
binding.composeView.setContent {
|
|
|
|
|
if (viewModel.drawerOpen.collectAsStateWithLifecycle().value) {
|
|
|
|
|
TasksTheme {
|
|
|
|
|
val sheetState = rememberModalBottomSheetState(
|
|
|
|
|
skipPartiallyExpanded = true,
|
|
|
|
|
confirmValueChange = { true },
|
|
|
|
|
)
|
|
|
|
|
ModalBottomSheet(
|
|
|
|
|
sheetState = sheetState,
|
|
|
|
|
containerColor = MaterialTheme.colorScheme.surface,
|
|
|
|
|
onDismissRequest = { viewModel.closeDrawer() },
|
|
|
|
|
) {
|
|
|
|
|
val state = viewModel.state.collectAsStateWithLifecycle().value
|
|
|
|
|
val context = LocalContext.current
|
|
|
|
|
val settingsRequest = rememberLauncherForActivityResult(
|
|
|
|
|
ActivityResultContracts.StartActivityForResult()
|
|
|
|
|
) {
|
|
|
|
|
context.findActivity()?.recreate()
|
|
|
|
|
}
|
|
|
|
|
val addAccountViewModel: AddAccountViewModel = hiltViewModel()
|
|
|
|
|
val microsoftVM: MicrosoftSignInViewModel = hiltViewModel()
|
|
|
|
|
val syncLauncher =
|
|
|
|
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
|
|
|
if (result.resultCode == RESULT_OK) {
|
|
|
|
|
syncAdapters.sync(true)
|
|
|
|
|
workManager.updateBackgroundSync()
|
|
|
|
|
} else {
|
|
|
|
|
result.data
|
|
|
|
|
?.getStringExtra(GtasksLoginActivity.EXTRA_ERROR)
|
|
|
|
|
?.let { toast(it) }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val importBackupLauncher =
|
|
|
|
|
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
|
|
|
if (result.resultCode == RESULT_OK) {
|
|
|
|
|
val uri = result.data?.data ?: return@rememberLauncherForActivityResult
|
|
|
|
|
ImportTasksDialog.newImportTasksDialog(uri)
|
|
|
|
|
.show(supportFragmentManager, FRAG_TAG_IMPORT_TASKS)
|
|
|
|
|
val scope = rememberCoroutineScope()
|
|
|
|
|
val bottomSearchBar = atLeastR()
|
|
|
|
|
TaskListDrawer(
|
|
|
|
|
arrangement = when {
|
|
|
|
|
state.menuQuery.isBlank() -> Arrangement.Top
|
|
|
|
|
bottomSearchBar -> Arrangement.Bottom
|
|
|
|
|
else -> Arrangement.Top
|
|
|
|
|
},
|
|
|
|
|
bottomSearchBar = bottomSearchBar,
|
|
|
|
|
filters = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems,
|
|
|
|
|
onClick = {
|
|
|
|
|
when (it) {
|
|
|
|
|
is DrawerItem.Filter -> {
|
|
|
|
|
viewModel.setFilter(it.filter)
|
|
|
|
|
scope.launch(Dispatchers.Default) {
|
|
|
|
|
sheetState.hide()
|
|
|
|
|
viewModel.closeDrawer()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
is DrawerItem.Header -> {
|
|
|
|
|
viewModel.toggleCollapsed(it.header)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AddAccountScreen(
|
|
|
|
|
gettingStarted = route.showImport,
|
|
|
|
|
hasTasksAccount = inventory.hasTasksAccount,
|
|
|
|
|
hasPro = inventory.hasPro,
|
|
|
|
|
onBack = { navController.popBackStack() },
|
|
|
|
|
signIn = { platform ->
|
|
|
|
|
firebase.logEvent(R.string.event_onboarding_sync, R.string.param_selection to platform)
|
|
|
|
|
when (platform) {
|
|
|
|
|
AddAccountDialog.Platform.TASKS_ORG ->
|
|
|
|
|
syncLauncher.launch(
|
|
|
|
|
Intent(this@MainActivity, SignInActivity::class.java)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
AddAccountDialog.Platform.GOOGLE_TASKS ->
|
|
|
|
|
syncLauncher.launch(
|
|
|
|
|
Intent(this@MainActivity, GtasksLoginActivity::class.java)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
AddAccountDialog.Platform.MICROSOFT ->
|
|
|
|
|
microsoftVM.signIn(this@MainActivity)
|
|
|
|
|
|
|
|
|
|
AddAccountDialog.Platform.CALDAV ->
|
|
|
|
|
syncLauncher.launch(
|
|
|
|
|
Intent(this@MainActivity, CaldavAccountSettingsActivity::class.java)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
AddAccountDialog.Platform.ETESYNC ->
|
|
|
|
|
syncLauncher.launch(
|
|
|
|
|
Intent(this@MainActivity, EtebaseAccountSettingsActivity::class.java)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
AddAccountDialog.Platform.LOCAL ->
|
|
|
|
|
addAccountViewModel.createLocalAccount()
|
|
|
|
|
|
|
|
|
|
else -> throw IllegalArgumentException()
|
|
|
|
|
},
|
|
|
|
|
onAddClick = {
|
|
|
|
|
scope.launch(Dispatchers.Default) {
|
|
|
|
|
sheetState.hide()
|
|
|
|
|
viewModel.closeDrawer()
|
|
|
|
|
when (it.header.addIntentRc) {
|
|
|
|
|
FilterProvider.REQUEST_NEW_FILTER ->
|
|
|
|
|
NewFilterDialog.newFilterDialog().show(
|
|
|
|
|
supportFragmentManager,
|
|
|
|
|
SubheaderClickHandler.FRAG_TAG_NEW_FILTER
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
REQUEST_NEW_PLACE ->
|
|
|
|
|
startActivityForResult(
|
|
|
|
|
Intent(
|
|
|
|
|
this@MainActivity,
|
|
|
|
|
LocationPickerActivity::class.java
|
|
|
|
|
),
|
|
|
|
|
REQUEST_NEW_PLACE
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
REQUEST_NEW_TAGS ->
|
|
|
|
|
startActivityForResult(
|
|
|
|
|
Intent(
|
|
|
|
|
this@MainActivity,
|
|
|
|
|
TagSettingsActivity::class.java
|
|
|
|
|
),
|
|
|
|
|
REQUEST_NEW_LIST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
REQUEST_NEW_LIST -> lifecycleScope.launch {
|
|
|
|
|
val account =
|
|
|
|
|
caldavDao.getAccount(it.header.id) ?: return@launch
|
|
|
|
|
when (it.header.subheaderType) {
|
|
|
|
|
NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS ->
|
|
|
|
|
startActivityForResult(
|
|
|
|
|
Intent(
|
|
|
|
|
this@MainActivity,
|
|
|
|
|
GoogleTaskListSettingsActivity::class.java
|
|
|
|
|
)
|
|
|
|
|
.putExtra(
|
|
|
|
|
GoogleTaskListSettingsActivity.EXTRA_ACCOUNT,
|
|
|
|
|
account
|
|
|
|
|
),
|
|
|
|
|
REQUEST_NEW_LIST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
NavigationDrawerSubheader.SubheaderType.CALDAV,
|
|
|
|
|
NavigationDrawerSubheader.SubheaderType.TASKS ->
|
|
|
|
|
startActivityForResult(
|
|
|
|
|
Intent(
|
|
|
|
|
this@MainActivity,
|
|
|
|
|
account.listSettingsClass()
|
|
|
|
|
)
|
|
|
|
|
.putExtra(
|
|
|
|
|
BaseCaldavCalendarSettingsActivity.EXTRA_CALDAV_ACCOUNT,
|
|
|
|
|
account
|
|
|
|
|
),
|
|
|
|
|
REQUEST_NEW_LIST
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
else -> {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> Timber.e("Unhandled request code: $it")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
openUrl = { platform ->
|
|
|
|
|
firebase.logEvent(R.string.event_onboarding_sync, R.string.param_selection to platform.name)
|
|
|
|
|
addAccountViewModel.openUrl(this@MainActivity, platform)
|
|
|
|
|
onErrorClick = {
|
|
|
|
|
context.startActivity(Intent(context, MainPreferences::class.java))
|
|
|
|
|
},
|
|
|
|
|
onImportBackup = {
|
|
|
|
|
firebase.logEvent(R.string.event_onboarding_sync, R.string.param_selection to "import_backup")
|
|
|
|
|
importBackupLauncher.launch(
|
|
|
|
|
FileHelper.newFilePickerIntent(this@MainActivity, preferences.backupDirectory),
|
|
|
|
|
searchBar = {
|
|
|
|
|
MenuSearchBar(
|
|
|
|
|
begForMoney = state.begForMoney,
|
|
|
|
|
onDrawerAction = {
|
|
|
|
|
viewModel.closeDrawer()
|
|
|
|
|
when (it) {
|
|
|
|
|
DrawerAction.PURCHASE ->
|
|
|
|
|
if (Tasks.IS_GENERIC)
|
|
|
|
|
context.openUri(R.string.url_donate)
|
|
|
|
|
else
|
|
|
|
|
context.startActivity(
|
|
|
|
|
Intent(
|
|
|
|
|
context,
|
|
|
|
|
PurchaseActivity::class.java
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
DrawerAction.SETTINGS ->
|
|
|
|
|
settingsRequest.launch(
|
|
|
|
|
Intent(
|
|
|
|
|
context,
|
|
|
|
|
MainPreferences::class.java
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
DrawerAction.HELP_AND_FEEDBACK ->
|
|
|
|
|
context.startActivity(
|
|
|
|
|
Intent(
|
|
|
|
|
context,
|
|
|
|
|
HelpAndFeedback::class.java
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
query = state.menuQuery,
|
|
|
|
|
onQueryChange = { viewModel.queryMenu(it) },
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
composable<HomeDestination> {
|
|
|
|
|
if (hasAccount != true) {
|
|
|
|
|
return@composable
|
|
|
|
|
}
|
|
|
|
|
val scope = rememberCoroutineScope()
|
|
|
|
|
val state = viewModel.state.collectAsStateWithLifecycle().value
|
|
|
|
|
val drawerState = rememberDrawerState(
|
|
|
|
|
initialValue = DrawerValue.Closed,
|
|
|
|
|
confirmStateChange = {
|
|
|
|
|
viewModel.setDrawerState(it == DrawerValue.Open)
|
|
|
|
|
true
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
val navigator = rememberListDetailPaneScaffoldNavigator(
|
|
|
|
|
calculatePaneScaffoldDirectiveWithTwoPanesOnMediumWidth(
|
|
|
|
|
windowAdaptiveInfo = currentWindowAdaptiveInfo(),
|
|
|
|
|
).copy(
|
|
|
|
|
horizontalPartitionSpacerSize = 0.dp,
|
|
|
|
|
verticalPartitionSpacerSize = 0.dp,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
val keyboard = LocalSoftwareKeyboardController.current
|
|
|
|
|
LaunchedEffect(state.task) {
|
|
|
|
|
val pane = if (state.task == null) {
|
|
|
|
|
ThreePaneScaffoldRole.Secondary
|
|
|
|
|
} else {
|
|
|
|
|
ThreePaneScaffoldRole.Primary
|
|
|
|
|
}
|
|
|
|
|
Timber.d("Navigating to $pane")
|
|
|
|
|
navigator.navigateTo(pane = pane)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val isDetailVisible =
|
|
|
|
|
navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded
|
|
|
|
|
BackHandler(enabled = state.task == null) {
|
|
|
|
|
Timber.d("onBackPressed")
|
|
|
|
|
if (isDetailVisible && navigator.canNavigateBack()) {
|
|
|
|
|
scope.launch {
|
|
|
|
|
navigator.navigateBack()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
finish()
|
|
|
|
|
if (!preferences.getBoolean(R.string.p_open_last_viewed_list, true)) {
|
|
|
|
|
runBlocking {
|
|
|
|
|
viewModel.resetFilter()
|
|
|
|
|
}
|
|
|
|
|
eventBus
|
|
|
|
|
.onEach(this::process)
|
|
|
|
|
.launchIn(lifecycleScope)
|
|
|
|
|
|
|
|
|
|
lifecycleScope.launch {
|
|
|
|
|
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
|
|
|
|
updateSystemBars(viewModel.state.value.filter)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
viewModel
|
|
|
|
|
.state
|
|
|
|
|
.flowWithLifecycle(lifecycle)
|
|
|
|
|
.map { it.filter to it.task }
|
|
|
|
|
.distinctUntilChanged()
|
|
|
|
|
.onEach { (newFilter, task) ->
|
|
|
|
|
Timber.d("filter: $newFilter task: $task")
|
|
|
|
|
val existingTlf =
|
|
|
|
|
supportFragmentManager.findFragmentByTag(FRAG_TAG_TASK_LIST) as TaskListFragment?
|
|
|
|
|
val existingFilter = existingTlf?.getFilter()
|
|
|
|
|
val tlf = if (
|
|
|
|
|
existingFilter != null
|
|
|
|
|
&& existingFilter.areItemsTheSame(newFilter)
|
|
|
|
|
&& existingFilter == newFilter
|
|
|
|
|
// && check if manual sort changed
|
|
|
|
|
) {
|
|
|
|
|
existingTlf
|
|
|
|
|
} else {
|
|
|
|
|
clearUi()
|
|
|
|
|
TaskListFragment.newTaskListFragment(newFilter)
|
|
|
|
|
}
|
|
|
|
|
val existingTef =
|
|
|
|
|
supportFragmentManager.findFragmentByTag(FRAG_TAG_TASK_EDIT) as TaskEditFragment?
|
|
|
|
|
val transaction = supportFragmentManager.beginTransaction()
|
|
|
|
|
if (task == null) {
|
|
|
|
|
if (intent.finishAffinity) {
|
|
|
|
|
finishAffinity()
|
|
|
|
|
} else if (existingTef != null) {
|
|
|
|
|
if (intent.removeTask && intent.broughtToFront) {
|
|
|
|
|
moveTaskToBack(true)
|
|
|
|
|
}
|
|
|
|
|
hideKeyboard()
|
|
|
|
|
transaction
|
|
|
|
|
.replace(R.id.detail, newEmptyTaskEditFragment())
|
|
|
|
|
.runOnCommit {
|
|
|
|
|
if (isSinglePaneLayout) {
|
|
|
|
|
binding.master.visibility = View.VISIBLE
|
|
|
|
|
binding.detail.visibility = View.GONE
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
LaunchedEffect(state.filter, state.task) {
|
|
|
|
|
actionMode?.finish()
|
|
|
|
|
actionMode = null
|
|
|
|
|
if (state.task == null) {
|
|
|
|
|
keyboard?.hide()
|
|
|
|
|
}
|
|
|
|
|
} else if (task != existingTef?.task) {
|
|
|
|
|
existingTef?.save(remove = false)
|
|
|
|
|
transaction
|
|
|
|
|
.replace(R.id.detail, newTaskEditFragment(task), FRAG_TAG_TASK_EDIT)
|
|
|
|
|
.runOnCommit {
|
|
|
|
|
if (isSinglePaneLayout) {
|
|
|
|
|
binding.detail.visibility = View.VISIBLE
|
|
|
|
|
binding.master.visibility = View.GONE
|
|
|
|
|
}
|
|
|
|
|
drawerState.close()
|
|
|
|
|
}
|
|
|
|
|
HomeScreen(
|
|
|
|
|
state = state,
|
|
|
|
|
drawerState = drawerState,
|
|
|
|
|
navigator = navigator,
|
|
|
|
|
showNewFilterDialog = {
|
|
|
|
|
NewFilterDialog.newFilterDialog().show(
|
|
|
|
|
supportFragmentManager,
|
|
|
|
|
SubheaderClickHandler.FRAG_TAG_NEW_FILTER
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
defaultFilterProvider.setLastViewedFilter(newFilter)
|
|
|
|
|
theme
|
|
|
|
|
.withThemeColor(getFilterColor(newFilter))
|
|
|
|
|
.applyToContext(this) // must happen before committing fragment
|
|
|
|
|
transaction
|
|
|
|
|
.replace(R.id.master, tlf, FRAG_TAG_TASK_LIST)
|
|
|
|
|
.runOnCommit { updateSystemBars(newFilter) }
|
|
|
|
|
.commit()
|
|
|
|
|
}
|
|
|
|
|
.launchIn(lifecycleScope)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun process(event: MainActivityEvent) = when (event) {
|
|
|
|
|
is MainActivityEvent.ClearTaskEditFragment ->
|
|
|
|
|
viewModel.setTask(null)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Deprecated("Deprecated in Java")
|
|
|
|
|
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
|
|
|
when (requestCode) {
|
|
|
|
|
REQUEST_NEW_LIST ->
|
|
|
|
|
if (resultCode == RESULT_OK && data != null) {
|
|
|
|
|
getParcelableExtra(data, OPEN_FILTER, Filter::class.java)?.let {
|
|
|
|
|
viewModel.setFilter(it)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
REQUEST_NEW_PLACE ->
|
|
|
|
|
if (resultCode == RESULT_OK && data != null) {
|
|
|
|
|
getParcelableExtra(data, EXTRA_PLACE, Place::class.java)?.let {
|
|
|
|
|
viewModel.setFilter(PlaceFilter(it))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else ->
|
|
|
|
|
super.onActivityResult(requestCode, resultCode, data)
|
|
|
|
|
}
|
|
|
|
|
logIntent("onCreate")
|
|
|
|
|
handleIntent()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onNewIntent(intent: Intent) {
|
|
|
|
|
@ -311,6 +393,12 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
handleIntent()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun clearUi() {
|
|
|
|
|
actionMode?.finish()
|
|
|
|
|
actionMode = null
|
|
|
|
|
viewModel.closeDrawer()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun getTaskToLoad(filter: Filter?): Task? = when {
|
|
|
|
|
intent.isFromHistory -> null
|
|
|
|
|
intent.hasExtra(CREATE_TASK) -> {
|
|
|
|
|
@ -333,13 +421,16 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
private fun logIntent(caller: String) {
|
|
|
|
|
if (BuildConfig.DEBUG) {
|
|
|
|
|
Timber.d("""
|
|
|
|
|
|$caller
|
|
|
|
|
|**********
|
|
|
|
|
|broughtToFront: ${intent.broughtToFront}
|
|
|
|
|
|isFromHistory: ${intent.isFromHistory}
|
|
|
|
|
|flags: ${intent.flagsToString}
|
|
|
|
|
${intent?.extras?.keySet()?.joinToString("\n") { "|$it: ${intent.extras?.get(it)}" } ?: "|NO EXTRAS"}
|
|
|
|
|
|**********""".trimMargin()
|
|
|
|
|
$caller
|
|
|
|
|
**********
|
|
|
|
|
broughtToFront: ${intent.broughtToFront}
|
|
|
|
|
isFromHistory: ${intent.isFromHistory}
|
|
|
|
|
flags: ${intent.flagsToString}
|
|
|
|
|
OPEN_FILTER: ${getParcelableExtra(intent, OPEN_FILTER, Filter::class.java)?.let { "${it.title}: $it" }}
|
|
|
|
|
LOAD_FILTER: ${intent.getStringExtra(LOAD_FILTER)}
|
|
|
|
|
OPEN_TASK: ${getParcelableExtra(intent, OPEN_TASK, Task::class.java)}
|
|
|
|
|
CREATE_TASK: ${intent.hasExtra(CREATE_TASK)}
|
|
|
|
|
**********""".trimIndent()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -354,36 +445,65 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun updateSystemBars(filter: Filter) {
|
|
|
|
|
with (getFilterColor(filter)) {
|
|
|
|
|
applyToNavigationBar(this@MainActivity)
|
|
|
|
|
applyTaskDescription(this@MainActivity, filter.title ?: getString(R.string.app_name))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun getFilterColor(filter: Filter) =
|
|
|
|
|
if (filter.tint != 0)
|
|
|
|
|
colorProvider.getThemeColor(filter.tint, true)
|
|
|
|
|
else
|
|
|
|
|
theme.themeColor
|
|
|
|
|
|
|
|
|
|
override fun onResume() {
|
|
|
|
|
super.onResume()
|
|
|
|
|
Timber.d("onResume")
|
|
|
|
|
if (currentNightMode != nightMode || currentPro != inventory.hasPro) {
|
|
|
|
|
restartActivity()
|
|
|
|
|
recreate()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if (preferences.getBoolean(R.string.p_just_updated, false)) {
|
|
|
|
|
if (preferences.getBoolean(R.string.p_show_whats_new, true)) {
|
|
|
|
|
val fragmentManager = supportFragmentManager
|
|
|
|
|
if (fragmentManager.findFragmentByTag(FRAG_TAG_WHATS_NEW) == null) {
|
|
|
|
|
WhatsNewDialog().show(fragmentManager, FRAG_TAG_WHATS_NEW)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
preferences.setBoolean(R.string.p_just_updated, false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override fun onPause() {
|
|
|
|
|
super.onPause()
|
|
|
|
|
Timber.d("onPause")
|
|
|
|
|
private suspend fun newTaskEditFragment(task: Task): TaskEditFragment {
|
|
|
|
|
AndroidUtilities.assertMainThread()
|
|
|
|
|
clearUi()
|
|
|
|
|
return coroutineScope {
|
|
|
|
|
withContext(Dispatchers.Default) {
|
|
|
|
|
val freshTask = async { if (task.isNew) task else taskDao.fetch(task.id) ?: task }
|
|
|
|
|
val list = async { defaultFilterProvider.getList(task) }
|
|
|
|
|
val location = async { locationDao.getLocation(task, preferences) }
|
|
|
|
|
val tags = async { tagDataDao.getTags(task) }
|
|
|
|
|
val alarms = async { alarmDao.getAlarms(task) }
|
|
|
|
|
newTaskEditFragment(
|
|
|
|
|
freshTask.await(),
|
|
|
|
|
list.await(),
|
|
|
|
|
location.await(),
|
|
|
|
|
tags.await(),
|
|
|
|
|
alarms.await(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val isSinglePaneLayout: Boolean
|
|
|
|
|
get() = !resources.getBoolean(R.bool.two_pane_layout)
|
|
|
|
|
|
|
|
|
|
override fun onSupportActionModeStarted(mode: ActionMode) {
|
|
|
|
|
super.onSupportActionModeStarted(mode)
|
|
|
|
|
actionMode = mode
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun restartActivity() {
|
|
|
|
|
finish()
|
|
|
|
|
startActivity(
|
|
|
|
|
Intent(this, MainActivity::class.java),
|
|
|
|
|
ActivityOptions.makeCustomAnimation(
|
|
|
|
|
this@MainActivity,
|
|
|
|
|
android.R.anim.fade_in, android.R.anim.fade_out
|
|
|
|
|
).toBundle()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
|
/** For indicating the new list screen should be launched at fragment setup time */
|
|
|
|
|
const val OPEN_FILTER = "open_filter" // $NON-NLS-1$
|
|
|
|
|
@ -393,6 +513,11 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
const val OPEN_TASK = "open_new_task" // $NON-NLS-1$
|
|
|
|
|
const val REMOVE_TASK = "remove_task"
|
|
|
|
|
const val FINISH_AFFINITY = "finish_affinity"
|
|
|
|
|
private const val FRAG_TAG_TASK_LIST = "frag_tag_task_list"
|
|
|
|
|
private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new"
|
|
|
|
|
private const val FRAG_TAG_TASK_EDIT = "frag_tag_task_edit"
|
|
|
|
|
private const val FLAG_FROM_HISTORY
|
|
|
|
|
= Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
|
|
|
|
|
|
|
|
|
val Intent.getFilter: Filter?
|
|
|
|
|
get() = if (isFromHistory) {
|
|
|
|
|
@ -415,17 +540,35 @@ class MainActivity : AppCompatActivity() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val Intent.removeTask: Boolean
|
|
|
|
|
get() = try {
|
|
|
|
|
getBooleanExtra(REMOVE_TASK, false) && !isFromHistory && !broughtToFront
|
|
|
|
|
} finally {
|
|
|
|
|
removeExtra(REMOVE_TASK)
|
|
|
|
|
get() = if (isFromHistory) {
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
getBooleanExtra(REMOVE_TASK, false).let {
|
|
|
|
|
removeExtra(REMOVE_TASK)
|
|
|
|
|
it
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val Intent.finishAffinity: Boolean
|
|
|
|
|
get() = try {
|
|
|
|
|
getBooleanExtra(FINISH_AFFINITY, false) && !isFromHistory && !broughtToFront
|
|
|
|
|
} finally {
|
|
|
|
|
removeExtra(FINISH_AFFINITY)
|
|
|
|
|
get() = if (isFromHistory) {
|
|
|
|
|
false
|
|
|
|
|
} else {
|
|
|
|
|
getBooleanExtra(FINISH_AFFINITY, false).let {
|
|
|
|
|
removeExtra(FINISH_AFFINITY)
|
|
|
|
|
it
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val Intent.isFromHistory: Boolean
|
|
|
|
|
get() = flags and FLAG_FROM_HISTORY == FLAG_FROM_HISTORY
|
|
|
|
|
|
|
|
|
|
val Intent.broughtToFront: Boolean
|
|
|
|
|
get() = flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT > 0
|
|
|
|
|
|
|
|
|
|
val Intent.flagsToString
|
|
|
|
|
get() = Intent::class.java.declaredFields
|
|
|
|
|
.filter { it.name.startsWith("FLAG_") }
|
|
|
|
|
.filter { flags or it.getInt(null) == flags }
|
|
|
|
|
.joinToString(" | ") { it.name }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|