diff --git a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt index 25cfa51f5..d4b1ab173 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt @@ -21,13 +21,14 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.core.content.IntentCompat.getParcelableExtra import androidx.lifecycle.Lifecycle +import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.google.android.material.composethemeadapter.MdcTheme import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.astrid.activity.TaskEditFragment.Companion.newTaskEditFragment -import com.todoroo.astrid.activity.TaskListFragment.TaskListFragmentCallbackHandler import com.todoroo.astrid.adapter.SubheaderClickHandler import com.todoroo.astrid.api.Filter import com.todoroo.astrid.dao.TaskDao @@ -37,12 +38,13 @@ 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.withContext import org.tasks.BuildConfig -import org.tasks.LocalBroadcastManager import org.tasks.R import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.activities.NavigationDrawerCustomization @@ -68,15 +70,13 @@ import org.tasks.extensions.Context.openUri import org.tasks.extensions.hideKeyboard import org.tasks.filters.FilterProvider import org.tasks.filters.PlaceFilter -import org.tasks.intents.TaskIntents.getTaskListIntent -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.themes.ColorProvider import org.tasks.themes.Theme -import org.tasks.themes.ThemeColor import org.tasks.ui.EmptyTaskEditFragment.Companion.newEmptyTaskEditFragment import org.tasks.ui.MainActivityEvent import org.tasks.ui.MainActivityEventBus @@ -84,12 +84,11 @@ import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint -class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { +class MainActivity : AppCompatActivity() { @Inject lateinit var preferences: Preferences @Inject lateinit var defaultFilterProvider: DefaultFilterProvider @Inject lateinit var theme: Theme @Inject lateinit var taskDao: TaskDao - @Inject lateinit var localBroadcastManager: LocalBroadcastManager @Inject lateinit var taskCreator: TaskCreator @Inject lateinit var inventory: Inventory @Inject lateinit var colorProvider: ColorProvider @@ -105,9 +104,6 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { private var actionMode: ActionMode? = null private lateinit var binding: TaskListActivityBinding - private val filter: Filter? - get() = viewModel.state.value.filter - private val settingsRequest = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { recreate() @@ -123,6 +119,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { currentPro = inventory.hasPro binding = TaskListActivityBinding.inflate(layoutInflater) setContentView(binding.root) + logIntent("onCreate") handleIntent() binding.composeView.setContent { @@ -164,7 +161,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { onClick = { when (it) { is DrawerItem.Filter -> { - openTaskListFragment(it.type()) + viewModel.setFilter(it.type()) scope.launch(Dispatchers.Default) { sheetState.hide() viewModel.setDrawerOpen(false) @@ -246,32 +243,95 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) { - applyTheme() + 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 + } + } + } + } 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 + } + } + } + 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 suspend fun process(event: MainActivityEvent) = when (event) { - is MainActivityEvent.OpenTask -> - onTaskListItemClicked(event.task) + private fun process(event: MainActivityEvent) = when (event) { is MainActivityEvent.ClearTaskEditFragment -> - removeTaskEditFragment() + 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 - ?.getParcelableExtra(OPEN_FILTER) - ?.let { startActivity(getTaskListIntent(this, it)) } + 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 - ?.getParcelableExtra(LocationPickerActivity.EXTRA_PLACE) - ?.let { startActivity(getTaskListIntent(this, PlaceFilter(it))) } + if (resultCode == RESULT_OK && data != null) { + getParcelableExtra(data, EXTRA_PLACE, Place::class.java)?.let { + viewModel.setFilter(PlaceFilter(it)) + } } + else -> super.onActivityResult(requestCode, resultCode, data) } @@ -280,185 +340,74 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) + logIntent("onNewIntent") handleIntent() } private fun clearUi() { - finishActionMode() + actionMode?.finish() + actionMode = null viewModel.setDrawerOpen(false) } - private suspend fun getTaskToLoad(filter: Filter?): Task? { - val intent = intent - if (intent.isFromHistory) { - return null - } - if (intent.hasExtra(CREATE_TASK)) { + private suspend fun getTaskToLoad(filter: Filter?): Task? = when { + intent.isFromHistory -> null + intent.hasExtra(CREATE_TASK) -> { val source = intent.getStringExtra(CREATE_SOURCE) firebase.addTask(source ?: "unknown") intent.removeExtra(CREATE_TASK) intent.removeExtra(CREATE_SOURCE) - return taskCreator.createWithValues(filter, "") + taskCreator.createWithValues(filter, "") } - if (intent.hasExtra(OPEN_TASK)) { - val task: Task? = intent.getParcelableExtra(OPEN_TASK) + + intent.hasExtra(OPEN_TASK) -> { + val task = getParcelableExtra(intent, OPEN_TASK, Task::class.java) intent.removeExtra(OPEN_TASK) - return task + task } - return null - } - private fun openTask(filter: Filter?) = lifecycleScope.launch { - val task = getTaskToLoad(filter) - when { - task != null -> onTaskListItemClicked(task) - taskEditFragment == null -> hideDetailFragment() - else -> showDetailFragment() - } + else -> null } - private fun handleIntent() { - val intent = intent - val openFilter = intent.getFilter - val loadFilter = intent.getFilterString - val openTask = !intent.isFromHistory - && (intent.hasExtra(OPEN_TASK) || intent.hasExtra(CREATE_TASK)) - val tef = taskEditFragment + private fun logIntent(caller: String) { if (BuildConfig.DEBUG) { - Timber.d( - """ - - ********** - broughtToFront: ${intent.broughtToFront} - isFromHistory: ${intent.isFromHistory} - flags: ${intent.flagsToString} - OPEN_FILTER: ${openFilter?.let { "${it.title}: $it" }} - LOAD_FILTER: $loadFilter - OPEN_TASK: ${intent.getParcelableExtra(OPEN_TASK)} - CREATE_TASK: ${intent.hasExtra(CREATE_TASK)} - taskListFragment: ${taskListFragment?.getFilter()?.let { "${it.title}: $it" }} - taskEditFragment: ${taskEditFragment?.editViewModel?.task} - **********""" + Timber.d(""" + $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() ) } - if (!openTask && (openFilter != null || !loadFilter.isNullOrBlank())) { - tef?.let { - lifecycleScope.launch { - it.save() - } - } - } - if (!loadFilter.isNullOrBlank() || openFilter == null && filter == null) { - lifecycleScope.launch { - val filter = if (loadFilter.isNullOrBlank()) { - defaultFilterProvider.getStartupFilter() - } else { - defaultFilterProvider.getFilterFromPreference(loadFilter) - } - clearUi() - if (isSinglePaneLayout) { - if (openTask) { - setFilter(filter) - openTask(filter) - } else { - openTaskListFragment(filter, true) - } - } else { - openTaskListFragment(filter, true) - openTask(filter) - } - } - } else if (openFilter != null) { - clearUi() - if (isSinglePaneLayout) { - if (openTask) { - setFilter(openFilter) - openTask(openFilter) - } else { - openTaskListFragment(openFilter, true) - } - } else { - openTaskListFragment(openFilter, true) - openTask(openFilter) - } - } else { - val existing = taskListFragment - val target = if (existing == null || existing.getFilter() !== filter) { - TaskListFragment.newTaskListFragment(applicationContext, filter) - } else { - existing - } - if (isSinglePaneLayout) { - if (openTask || tef != null) { - openTask(filter) - } else { - openTaskListFragment(filter, false) - } - } else { - openTaskListFragment(target, false) - openTask(filter) - } - } } - private fun showDetailFragment() { - if (isSinglePaneLayout) { - binding.detail.visibility = View.VISIBLE - binding.master.visibility = View.GONE + private fun handleIntent() { + lifecycleScope.launch { + val filter = intent.getFilter + ?: intent.getFilterString?.let { defaultFilterProvider.getFilterFromPreference(it) } + ?: viewModel.state.value.filter + val task = getTaskToLoad(filter) + viewModel.setFilter(filter = filter, task = task) } } - private fun hideDetailFragment() { - supportFragmentManager - .beginTransaction() - .replace(R.id.detail, newEmptyTaskEditFragment()) - .runOnCommit { - if (isSinglePaneLayout) { - binding.master.visibility = View.VISIBLE - binding.detail.visibility = View.GONE - } - } - .commit() - } - - private fun setFilter(newFilter: Filter?) { - newFilter?.let { - viewModel.setFilter(it) - applyTheme() + private fun updateSystemBars(filter: Filter) { + with (getFilterColor(filter)) { + applyToNavigationBar(this@MainActivity) + applyTaskDescription(this@MainActivity, filter.title ?: getString(R.string.app_name)) } } - private fun openTaskListFragment(filter: Filter?, force: Boolean = false) { - openTaskListFragment(TaskListFragment.newTaskListFragment(applicationContext, filter), force) - } - - private fun openTaskListFragment(taskListFragment: TaskListFragment, force: Boolean) { - AndroidUtilities.assertMainThread() - if (supportFragmentManager.isDestroyed) { - return - } - val newFilter = taskListFragment.getFilter() - if (!force && filter == newFilter) { - return - } - viewModel.setFilter(newFilter) - applyTheme() - supportFragmentManager - .beginTransaction() - .replace(R.id.master, taskListFragment, FRAG_TAG_TASK_LIST) - .commitNowAllowingStateLoss() - } - - private fun applyTheme() { - val filterColor = filterColor - filterColor.applyToNavigationBar(this) - filterColor.applyTaskDescription(this, filter?.title ?: getString(R.string.app_name)) - theme.withThemeColor(filterColor).applyToContext(this) - } - - private val filterColor: ThemeColor - get() = filter?.tint?.takeIf { it != 0 } - ?.let { colorProvider.getThemeColor(it, true) } ?: theme.themeColor + private fun getFilterColor(filter: Filter) = + if (filter.tint != 0) + colorProvider.getThemeColor(filter.tint, true) + else + theme.themeColor override fun onResume() { super.onResume() @@ -477,78 +426,35 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { } } - override suspend fun onTaskListItemClicked(task: Task?) { + private suspend fun newTaskEditFragment(task: Task): TaskEditFragment { AndroidUtilities.assertMainThread() - if (task == null) { - return - } - taskEditFragment?.save(remove = false) clearUi() - coroutineScope { - 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) } - val fragment = withContext(Dispatchers.Default) { + 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(), + freshTask.await(), + list.await(), + location.await(), + tags.await(), + alarms.await(), ) } - supportFragmentManager.beginTransaction() - .replace(R.id.detail, fragment, TaskEditFragment.TAG_TASKEDIT_FRAGMENT) - .runOnCommit { showDetailFragment() } - .commitNowAllowingStateLoss() - } } - override fun onNavigationIconClicked() { - hideKeyboard() - viewModel.setDrawerOpen(true) - } - - private val taskListFragment: TaskListFragment? - get() = supportFragmentManager.findFragmentByTag(FRAG_TAG_TASK_LIST) as TaskListFragment? - - private val taskEditFragment: TaskEditFragment? - get() = supportFragmentManager.findFragmentByTag(TaskEditFragment.TAG_TASKEDIT_FRAGMENT) as TaskEditFragment? - private val isSinglePaneLayout: Boolean get() = !resources.getBoolean(R.bool.two_pane_layout) - private fun removeTaskEditFragment() { - val removeTask = intent.removeTask - val finishAffinity = intent.finishAffinity - if (finishAffinity || taskListFragment == null) { - finishAffinity() - } else { - if (removeTask && intent.broughtToFront) { - moveTaskToBack(true) - } - hideKeyboard() - hideDetailFragment() - taskListFragment?.let { - setFilter(it.getFilter()) - it.loadTaskListContent() - } - } - } - override fun onSupportActionModeStarted(mode: ActionMode) { super.onSupportActionModeStarted(mode) actionMode = mode } - private fun finishActionMode() { - actionMode?.finish() - actionMode = null - } - companion object { /** For indicating the new list screen should be launched at fragment setup time */ const val OPEN_FILTER = "open_filter" // $NON-NLS-1$ @@ -560,6 +466,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { 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 const val REQUEST_NEW_LIST = 10100 @@ -569,7 +476,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler { get() = if (isFromHistory) { null } else { - getParcelableExtra(OPEN_FILTER)?.let { + getParcelableExtra(this, OPEN_FILTER, Filter::class.java)?.let { removeExtra(OPEN_FILTER) it } diff --git a/app/src/main/java/com/todoroo/astrid/activity/MainActivityViewModel.kt b/app/src/main/java/com/todoroo/astrid/activity/MainActivityViewModel.kt index fa2ec5b5f..c0e421013 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/MainActivityViewModel.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/MainActivityViewModel.kt @@ -1,18 +1,22 @@ package com.todoroo.astrid.activity -import android.annotation.SuppressLint import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.todoroo.astrid.activity.MainActivity.Companion.LOAD_FILTER +import com.todoroo.astrid.activity.MainActivity.Companion.OPEN_FILTER import com.todoroo.astrid.api.CaldavFilter import com.todoroo.astrid.api.CustomFilter import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter.Companion.NO_COUNT import com.todoroo.astrid.api.GtasksFilter import com.todoroo.astrid.api.TagFilter +import com.todoroo.astrid.data.Task import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList @@ -21,6 +25,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.tasks.LocalBroadcastManager import org.tasks.R import org.tasks.Tasks.Companion.IS_GENERIC @@ -39,8 +44,9 @@ import timber.log.Timber import javax.inject.Inject @HiltViewModel -@SuppressLint("StaticFieldLeak") class MainActivityViewModel @Inject constructor( + savedStateHandle: SavedStateHandle, + @ApplicationContext context: Context, private val defaultFilterProvider: DefaultFilterProvider, private val filterProvider: FilterProvider, private val taskDao: TaskDao, @@ -53,12 +59,22 @@ class MainActivityViewModel @Inject constructor( data class State( val begForMoney: Boolean = false, - val filter: Filter? = null, + val filter: Filter, + val task: Task? = null, val drawerOpen: Boolean = false, val drawerItems: ImmutableList = persistentListOf(), ) - private val _state = MutableStateFlow(State()) + private val _state = MutableStateFlow( + State( + filter = savedStateHandle.get(OPEN_FILTER) + ?: savedStateHandle.get(LOAD_FILTER)?.let { + runBlocking { defaultFilterProvider.getFilterFromPreference(it) } + } + ?: runBlocking { defaultFilterProvider.getStartupFilter() }, + begForMoney = if (IS_GENERIC) !inventory.hasTasksAccount else !inventory.hasPro, + ) + ) val state = _state.asStateFlow() private val refreshReceiver = object : BroadcastReceiver() { @@ -70,10 +86,18 @@ class MainActivityViewModel @Inject constructor( } } - fun setFilter(filter: Filter) { - _state.update { it.copy(filter = filter) } + fun setFilter( + filter: Filter, + task: Task? = null, + ) { + _state.update { + it.copy( + filter = filter, + task = task, + ) + } updateFilters() - defaultFilterProvider.lastViewedFilter = filter + defaultFilterProvider.setLastViewedFilter(filter) } fun setDrawerOpen(open: Boolean) { @@ -83,11 +107,6 @@ class MainActivityViewModel @Inject constructor( init { localBroadcastManager.registerRefreshListReceiver(refreshReceiver) updateFilters() - _state.update { - it.copy( - begForMoney = if (IS_GENERIC) !inventory.hasTasksAccount else !inventory.hasPro - ) - } } override fun onCleared() { @@ -171,7 +190,10 @@ class MainActivityViewModel @Inject constructor( caldavDao.setCollapsed(subheader.id, collapsed) localBroadcastManager.broadcastRefreshList() } - else -> {} } } -} \ No newline at end of file + + fun setTask(task: Task?) { + _state.update { it.copy(task = task) } + } +} diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt index 1a4951bed..d6f721d2c 100755 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskEditFragment.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.viewinterop.AndroidViewBinding import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.os.BundleCompat import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -139,6 +140,9 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener { activity?.recreate() } + val task: Task? + get() = BundleCompat.getParcelable(requireArguments(), EXTRA_TASK, Task::class.java) + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { requireActivity().onBackPressedDispatcher.addCallback(owner = viewLifecycleOwner) { @@ -514,7 +518,6 @@ class TaskEditFragment : Fragment(), Toolbar.OnMenuItemClickListener { } companion object { - const val TAG_TASKEDIT_FRAGMENT = "taskedit_fragment" const val EXTRA_TASK = "extra_task" const val EXTRA_LIST = "extra_list" const val EXTRA_LOCATION = "extra_location" diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index 643131e91..32757531c 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -34,6 +34,7 @@ import androidx.core.view.forEach import androidx.core.view.isVisible import androidx.core.view.setMargins import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -105,9 +106,9 @@ import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.toast import org.tasks.extensions.Fragment.safeStartActivityForResult import org.tasks.extensions.formatNumber +import org.tasks.extensions.hideKeyboard import org.tasks.extensions.setOnQueryTextListener import org.tasks.filters.PlaceFilter -import org.tasks.intents.TaskIntents import org.tasks.preferences.Device import org.tasks.preferences.Preferences import org.tasks.sync.SyncAdapters @@ -160,13 +161,13 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL @Inject lateinit var taskEditEventBus: TaskEditEventBus private val listViewModel: TaskListViewModel by viewModels() + private val mainViewModel: MainActivityViewModel by activityViewModels() private lateinit var taskAdapter: TaskAdapter private var recyclerAdapter: DragAndDropRecyclerAdapter? = null private lateinit var filter: Filter private lateinit var search: MenuItem private var mode: ActionMode? = null lateinit var themeColor: ThemeColor - private lateinit var callbacks: TaskListFragmentCallbackHandler private lateinit var binding: FragmentTaskListBinding private val onBackPressed = object : OnBackPressedCallback(false) { override fun handleOnBackPressed() { @@ -195,10 +196,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL val data = result.data ?: return@registerForActivityResult when (data.action) { ACTION_DELETED -> - openFilter(BuiltInFilterExposer.getMyTasksFilter(resources)) + mainViewModel.setFilter(BuiltInFilterExposer.getMyTasksFilter(resources)) ACTION_RELOAD -> IntentCompat.getParcelableExtra(data, MainActivity.OPEN_FILTER, Filter::class.java)?.let { - openFilter(it) + mainViewModel.setFilter(it) } } } @@ -227,11 +228,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL } } - override fun onAttach(context: Context) { - super.onAttach(requireContext()) - callbacks = activity as TaskListFragmentCallbackHandler - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) val selectedTaskIds: List = taskAdapter.getSelected() @@ -325,7 +321,10 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL (binding.toolbar.layoutParams as AppBarLayout.LayoutParams).scrollFlags = 0 } toolbar.setOnMenuItemClickListener(this) - toolbar.setNavigationOnClickListener { callbacks.onNavigationIconClicked() } + toolbar.setNavigationOnClickListener { + activity?.hideKeyboard() + mainViewModel.setDrawerOpen(true) + } setupMenu(toolbar) childFragmentManager.setFilterPickerResultListener(this) { val selected = taskAdapter.getSelected() @@ -411,14 +410,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL menu.findItem(R.id.menu_clear_completed).isVisible = filter.isWritable } - private fun openFilter(filter: Filter?) { - if (filter == null) { - startActivity(TaskIntents.getTaskListByIdIntent(context, null)) - } else { - startActivity(TaskIntents.getTaskListIntent(context, filter)) - } - } - override fun onMenuItemClick(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_voice_add -> { @@ -649,7 +640,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL } private fun onTaskListItemClicked(task: Task?) = lifecycleScope.launch { - callbacks.onTaskListItemClicked(task) + mainViewModel.setTask(task) } override fun onMenuItemActionExpand(item: MenuItem): Boolean { @@ -671,7 +662,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL } override fun onQueryTextSubmit(query: String): Boolean { - openFilter(requireContext().createSearchQuery(query.trim())) + mainViewModel.setFilter(requireContext().createSearchQuery(query.trim())) search.collapseActionView() return true } @@ -828,11 +819,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL } } - interface TaskListFragmentCallbackHandler { - suspend fun onTaskListItemClicked(task: Task?) - fun onNavigationIconClicked() - } - val isActionModeActive: Boolean get() = mode != null @@ -911,8 +897,7 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL override fun onClick(filter: Filter) { if (!isActionModeActive) { - val context = activity - context?.startActivity(TaskIntents.getTaskListIntent(context, filter)) + mainViewModel.setFilter(filter) } } @@ -998,12 +983,11 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL private const val FRAG_TAG_DATE_TIME_PICKER = "frag_tag_date_time_picker" private const val FRAG_TAG_PRIORITY_PICKER = "frag_tag_priority_picker" private const val REQUEST_TAG_TASKS = 10106 - fun newTaskListFragment(context: Context, filter: Filter?): TaskListFragment { + + fun newTaskListFragment(filter: Filter): TaskListFragment { val fragment = TaskListFragment() val bundle = Bundle() - bundle.putParcelable( - EXTRA_FILTER, - filter ?: BuiltInFilterExposer.getMyTasksFilter(context.resources)) + bundle.putParcelable(EXTRA_FILTER, filter) fragment.arguments = bundle return fragment } diff --git a/app/src/main/java/org/tasks/extensions/Context.kt b/app/src/main/java/org/tasks/extensions/Context.kt index dab0c4d86..a8ff1acba 100644 --- a/app/src/main/java/org/tasks/extensions/Context.kt +++ b/app/src/main/java/org/tasks/extensions/Context.kt @@ -42,6 +42,10 @@ object Context { val Context.isNightMode: Boolean get() = nightMode == Configuration.UI_MODE_NIGHT_YES + @Deprecated("Not supposed to use this") + val Context.isSinglePaneLayout: Boolean + get() = !resources.getBoolean(R.bool.two_pane_layout) + fun Context.openUri(resId: Int, vararg formatArgs: Any) = openUri(getString(resId, formatArgs)) fun Context.openUri(url: String?) = diff --git a/app/src/main/java/org/tasks/preferences/DefaultFilterProvider.kt b/app/src/main/java/org/tasks/preferences/DefaultFilterProvider.kt index 63b6dedc7..6f689e8e5 100644 --- a/app/src/main/java/org/tasks/preferences/DefaultFilterProvider.kt +++ b/app/src/main/java/org/tasks/preferences/DefaultFilterProvider.kt @@ -41,10 +41,6 @@ class DefaultFilterProvider @Inject constructor( @Deprecated("use coroutines") get() = runBlocking { getFilterFromPreference(R.string.p_dashclock_filter) } set(filter) = setFilterPreference(filter, R.string.p_dashclock_filter) - var lastViewedFilter: Filter - @Deprecated("use coroutines") get() = runBlocking { getFilterFromPreference(R.string.p_last_viewed_list) } - set(filter) = setFilterPreference(filter, R.string.p_last_viewed_list) - var defaultList: Filter @Deprecated("use coroutines") get() = runBlocking { getDefaultList() } set(filter) = setFilterPreference(filter, R.string.p_default_list) @@ -62,6 +58,8 @@ class DefaultFilterProvider @Inject constructor( ?.takeIf { it.isWritable } ?: getAnyList() + fun setLastViewedFilter(filter: Filter) = setFilterPreference(filter, R.string.p_last_viewed_list) + private suspend fun getLastViewedFilter() = getFilterFromPreference(R.string.p_last_viewed_list) suspend fun getDefaultOpenFilter() = getFilterFromPreference(R.string.p_default_open_filter) diff --git a/app/src/main/java/org/tasks/ui/MainActivityEvent.kt b/app/src/main/java/org/tasks/ui/MainActivityEvent.kt index 13532746d..0a74e2711 100644 --- a/app/src/main/java/org/tasks/ui/MainActivityEvent.kt +++ b/app/src/main/java/org/tasks/ui/MainActivityEvent.kt @@ -1,12 +1,10 @@ package org.tasks.ui -import com.todoroo.astrid.data.Task import kotlinx.coroutines.flow.MutableSharedFlow typealias MainActivityEventBus = MutableSharedFlow sealed interface MainActivityEvent { - data class OpenTask(val task: Task) : MainActivityEvent - object ClearTaskEditFragment : MainActivityEvent + data object ClearTaskEditFragment : MainActivityEvent } diff --git a/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt index c2877ce7e..d33978f0a 100644 --- a/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt +++ b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt @@ -5,12 +5,14 @@ import android.os.Bundle import android.view.View import android.view.ViewGroup import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.activityViewModels import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.google.android.material.composethemeadapter.MdcTheme import com.todoroo.andlib.sql.Criterion import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.utility.DateUtilities.now +import com.todoroo.astrid.activity.MainActivityViewModel import com.todoroo.astrid.api.FilterImpl import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.data.Task @@ -36,11 +38,11 @@ class SubtaskControlSet : TaskEditControlFragment() { @Inject lateinit var taskDao: TaskDao @Inject lateinit var checkBoxProvider: CheckBoxProvider @Inject lateinit var chipProvider: ChipProvider - @Inject lateinit var eventBus: MainActivityEventBus @Inject lateinit var colorProvider: ColorProvider @Inject lateinit var preferences: Preferences private lateinit var listViewModel: TaskListViewModel + private val mainViewModel: MainActivityViewModel by activityViewModels() override fun createView(savedInstanceState: Bundle?) { viewModel.task.takeIf { it.id > 0 }?.let { @@ -92,7 +94,7 @@ class SubtaskControlSet : TaskEditControlFragment() { } private fun openSubtask(task: Task) = lifecycleScope.launch { - eventBus.emit(MainActivityEvent.OpenTask(task)) + mainViewModel.setTask(task) } private fun toggleSubtask(taskId: Long, collapsed: Boolean) = lifecycleScope.launch { diff --git a/compose-metrics/app_googleplayDebug-classes.txt b/compose-metrics/app_googleplayDebug-classes.txt index 444720203..e271276f1 100644 --- a/compose-metrics/app_googleplayDebug-classes.txt +++ b/compose-metrics/app_googleplayDebug-classes.txt @@ -139,7 +139,6 @@ unstable class MainActivity { unstable var defaultFilterProvider: DefaultFilterProvider unstable var theme: Theme unstable var taskDao: TaskDao - unstable var localBroadcastManager: LocalBroadcastManager unstable var taskCreator: TaskCreator unstable var inventory: Inventory unstable var colorProvider: ColorProvider @@ -156,12 +155,13 @@ unstable class MainActivity { unstable val settingsRequest: ActivityResultLauncher<@[FlexibleNullability] Intent?> = Unstable } -stable class State { +unstable class State { stable val begForMoney: Boolean - runtime val filter: Filter? + runtime val filter: Filter + unstable val task: Task? stable val drawerOpen: Boolean runtime val drawerItems: ImmutableList - = + = Unstable } unstable class MainActivityViewModel { unstable val defaultFilterProvider: DefaultFilterProvider @@ -231,13 +231,13 @@ unstable class TaskListFragment { unstable var taskListEventBus: MutableSharedFlow{ org.tasks.ui.TaskListEventBus } unstable var taskEditEventBus: MutableSharedFlow{ org.tasks.ui.TaskEditEventBus } unstable val listViewModel$delegate: Lazy + unstable val mainViewModel$delegate: Lazy unstable var taskAdapter: TaskAdapter unstable var recyclerAdapter: DragAndDropRecyclerAdapter? runtime var filter: Filter unstable var search: MenuItem unstable var mode: ActionMode? unstable var themeColor: ThemeColor - runtime var callbacks: TaskListFragmentCallbackHandler unstable var binding: FragmentTaskListBinding stable val onBackPressed: unstable val sortRequest: ActivityResultLauncher<@[FlexibleNullability] Intent?> @@ -3071,10 +3071,6 @@ unstable class LocationControlSet { unstable var permissionChecker: PermissionChecker = Unstable } -unstable class OpenTask { - unstable val task: Task - = Unstable -} stable class ClearTaskEditFragment { = Stable } @@ -3086,10 +3082,10 @@ unstable class SubtaskControlSet { unstable var taskDao: TaskDao unstable var checkBoxProvider: CheckBoxProvider unstable var chipProvider: ChipProvider - unstable var eventBus: MutableSharedFlow{ org.tasks.ui.MainActivityEventBus } unstable var colorProvider: ColorProvider unstable var preferences: Preferences unstable var listViewModel: TaskListViewModel + unstable val mainViewModel$delegate: Lazy = Unstable } unstable class TaskEditControlFragment { diff --git a/compose-metrics/app_googleplayDebug-composables.txt b/compose-metrics/app_googleplayDebug-composables.txt index 631c80666..0052be52c 100644 --- a/compose-metrics/app_googleplayDebug-composables.txt +++ b/compose-metrics/app_googleplayDebug-composables.txt @@ -510,6 +510,7 @@ restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HeaderIte stable onErrorClick: Function0 ) restartable skippable scheme("[androidx.compose.ui.UiComposable, [androidx.compose.ui.UiComposable]]") fun MenuRow( + stable modifier: Modifier? = @static Companion stable padding: PaddingValues? = @static PaddingValues( horizontal = 16 . dp ) diff --git a/compose-metrics/app_googleplayDebug-module.json b/compose-metrics/app_googleplayDebug-module.json index 31b4eabcd..03e555984 100644 --- a/compose-metrics/app_googleplayDebug-module.json +++ b/compose-metrics/app_googleplayDebug-module.json @@ -4,19 +4,19 @@ "readonlyComposables": 0, "totalComposables": 544, "restartGroups": 531, - "totalGroups": 659, + "totalGroups": 660, "staticArguments": 802, "certainArguments": 360, - "knownStableArguments": 5198, + "knownStableArguments": 5201, "knownUnstableArguments": 150, "unknownStableArguments": 11, - "totalArguments": 5359, + "totalArguments": 5362, "markedStableClasses": 2, - "inferredStableClasses": 108, + "inferredStableClasses": 107, "inferredUnstableClasses": 334, "inferredUncertainClasses": 1, - "effectivelyStableClasses": 110, - "totalClasses": 445, + "effectivelyStableClasses": 109, + "totalClasses": 444, "memoizedLambdas": 572, "singletonLambdas": 195, "singletonComposableLambdas": 97,