Add TasksMenu composable

pull/2803/head
Alex Baker 2 months ago
parent b2efb42d55
commit d686b8c7e0

@ -12,15 +12,9 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.compose.material.MaterialTheme import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.runtime.LaunchedEffect import androidx.compose.foundation.layout.mandatorySystemGestures
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
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.core.content.IntentCompat.getParcelableExtra
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
@ -34,7 +28,6 @@ import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import com.todoroo.astrid.service.TaskCreator import com.todoroo.astrid.service.TaskCreator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -42,18 +35,10 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.billing.PurchaseActivity
import org.tasks.compose.collectAsStateLifecycleAware import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.drawer.DrawerAction import org.tasks.compose.drawer.TasksMenu
import org.tasks.compose.drawer.DrawerItem
import org.tasks.compose.drawer.ModalBottomSheet
import org.tasks.compose.drawer.SheetState
import org.tasks.compose.drawer.SheetValue
import org.tasks.compose.drawer.TaskListDrawer
import org.tasks.data.AlarmDao import org.tasks.data.AlarmDao
import org.tasks.data.LocationDao import org.tasks.data.LocationDao
import org.tasks.data.Place import org.tasks.data.Place
@ -62,14 +47,11 @@ import org.tasks.databinding.TaskListActivityBinding
import org.tasks.dialogs.NewFilterDialog import org.tasks.dialogs.NewFilterDialog
import org.tasks.dialogs.WhatsNewDialog import org.tasks.dialogs.WhatsNewDialog
import org.tasks.extensions.Context.nightMode import org.tasks.extensions.Context.nightMode
import org.tasks.extensions.Context.openUri
import org.tasks.extensions.hideKeyboard import org.tasks.extensions.hideKeyboard
import org.tasks.filters.FilterProvider import org.tasks.filters.FilterProvider
import org.tasks.filters.PlaceFilter import org.tasks.filters.PlaceFilter
import org.tasks.location.LocationPickerActivity.Companion.EXTRA_PLACE import org.tasks.location.LocationPickerActivity.Companion.EXTRA_PLACE
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.HelpAndFeedback
import org.tasks.preferences.MainPreferences
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider import org.tasks.themes.ColorProvider
import org.tasks.themes.Theme import org.tasks.themes.Theme
@ -107,7 +89,6 @@ class MainActivity : AppCompatActivity() {
/** @see android.app.Activity.onCreate /** @see android.app.Activity.onCreate
*/ */
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
theme.applyTheme(this) theme.applyTheme(this)
@ -122,113 +103,29 @@ class MainActivity : AppCompatActivity() {
val state = viewModel.state.collectAsStateLifecycleAware().value val state = viewModel.state.collectAsStateLifecycleAware().value
if (state.drawerOpen) { if (state.drawerOpen) {
MdcTheme { MdcTheme {
var expanded by remember { mutableStateOf(false) } TasksMenu(
val skipPartiallyExpanded = remember(expanded) { bottomPadding = WindowInsets.mandatorySystemGestures
expanded || preferences.isTopAppBar .asPaddingValues()
} .calculateBottomPadding(),
val sheetState = rememberSaveable( items = state.drawerItems,
skipPartiallyExpanded, begForMoney = state.begForMoney,
saver = SheetState.Saver( isTopAppBar = preferences.isTopAppBar,
skipPartiallyExpanded = skipPartiallyExpanded, setFilter = { viewModel.setFilter(it) },
confirmValueChange = { true }, toggleCollapsed = { viewModel.toggleCollapsed(it) },
) addFilter = {
) { val rc = it.addIntentRc
SheetState( if (rc == FilterProvider.REQUEST_NEW_FILTER) {
skipPartiallyExpanded = skipPartiallyExpanded, NewFilterDialog.newFilterDialog().show(
initialValue = if (skipPartiallyExpanded) SheetValue.Expanded else SheetValue.PartiallyExpanded, supportFragmentManager,
confirmValueChange = { true }, SubheaderClickHandler.FRAG_TAG_NEW_FILTER
skipHiddenState = false, )
) } else {
} val intent = it.addIntent ?: return@TasksMenu
LaunchedEffect(sheetState.currentValue) { startActivityForResult(intent, rc)
if (sheetState.currentValue == SheetValue.Expanded) { }
expanded = true },
} dismiss = { viewModel.setDrawerOpen(false) },
} )
ModalBottomSheet(
sheetState = sheetState,
containerColor = MaterialTheme.colors.surface,
onDismissRequest = { viewModel.setDrawerOpen(false) }
) {
val scope = rememberCoroutineScope()
TaskListDrawer(
begForMoney = state.begForMoney,
filters = state.drawerItems,
onClick = {
when (it) {
is DrawerItem.Filter -> {
viewModel.setFilter(it.type())
scope.launch(Dispatchers.Default) {
sheetState.hide()
viewModel.setDrawerOpen(false)
}
}
is DrawerItem.Header -> {
viewModel.toggleCollapsed(it.type())
}
}
},
onAddClick = {
scope.launch(Dispatchers.Default) {
sheetState.hide()
viewModel.setDrawerOpen(false)
val subheaderType = it.type()
val rc = subheaderType.addIntentRc
if (rc == FilterProvider.REQUEST_NEW_FILTER) {
NewFilterDialog.newFilterDialog().show(
supportFragmentManager,
SubheaderClickHandler.FRAG_TAG_NEW_FILTER
)
} else {
val intent = subheaderType.addIntent ?: return@launch
startActivityForResult(intent, rc)
}
}
},
onDrawerAction = {
viewModel.setDrawerOpen(false)
when (it) {
DrawerAction.PURCHASE ->
if (IS_GENERIC)
openUri(R.string.url_donate)
else
startActivity(
Intent(
this@MainActivity,
PurchaseActivity::class.java
)
)
DrawerAction.CUSTOMIZE_DRAWER ->
startActivity(
Intent(
this@MainActivity,
NavigationDrawerCustomization::class.java
)
)
DrawerAction.SETTINGS ->
settingsRequest.launch(
Intent(
this@MainActivity,
MainPreferences::class.java
)
)
DrawerAction.HELP_AND_FEEDBACK ->
startActivity(
Intent(
this@MainActivity,
HelpAndFeedback::class.java
)
)
}
},
onErrorClick = {
startActivity(Intent(this@MainActivity, MainPreferences::class.java))
},
)
}
} }
} }
} }

@ -14,11 +14,8 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.mandatorySystemGestures
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
@ -46,6 +43,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.google.android.material.composethemeadapter.MdcTheme import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.astrid.api.FilterImpl import com.todoroo.astrid.api.FilterImpl
@ -58,6 +56,7 @@ import org.tasks.filters.NavigationDrawerSubheader
@Composable @Composable
fun TaskListDrawer( fun TaskListDrawer(
bottomPadding: Dp = 0.dp,
begForMoney: Boolean, begForMoney: Boolean,
filters: ImmutableList<DrawerItem>, filters: ImmutableList<DrawerItem>,
onClick: (DrawerItem) -> Unit, onClick: (DrawerItem) -> Unit,
@ -65,14 +64,9 @@ fun TaskListDrawer(
onAddClick: (DrawerItem.Header) -> Unit, onAddClick: (DrawerItem.Header) -> Unit,
onErrorClick: () -> Unit, onErrorClick: () -> Unit,
) { ) {
val insets = WindowInsets.mandatorySystemGestures
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
.padding( .padding(bottom = bottomPadding)
bottom = insets
.asPaddingValues()
.calculateBottomPadding()
)
.animateContentSize( .animateContentSize(
animationSpec = spring( animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy, dampingRatio = Spring.DampingRatioNoBouncy,

@ -0,0 +1,133 @@
package org.tasks.compose.drawer
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3Api
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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.todoroo.astrid.api.Filter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.Tasks
import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.billing.PurchaseActivity
import org.tasks.extensions.Context.findActivity
import org.tasks.extensions.Context.openUri
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.preferences.HelpAndFeedback
import org.tasks.preferences.MainPreferences
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TasksMenu(
bottomPadding: Dp = 0.dp,
items: ImmutableList<DrawerItem>,
isTopAppBar: Boolean,
begForMoney: Boolean,
setFilter: (Filter) -> Unit,
toggleCollapsed: (NavigationDrawerSubheader) -> Unit,
addFilter: (NavigationDrawerSubheader) -> Unit,
dismiss: () -> Unit,
) {
var expanded by remember { mutableStateOf(false) }
val skipPartiallyExpanded = remember(expanded) {
expanded || isTopAppBar
}
val density = LocalDensity.current
val sheetState = rememberSaveable(
skipPartiallyExpanded,
saver = SheetState.Saver(
skipPartiallyExpanded = skipPartiallyExpanded,
confirmValueChange = { true },
density = density,
)
) {
SheetState(
skipPartiallyExpanded = skipPartiallyExpanded,
initialValue = if (skipPartiallyExpanded) SheetValue.Expanded else SheetValue.PartiallyExpanded,
confirmValueChange = { true },
skipHiddenState = false,
density = density,
)
}
LaunchedEffect(sheetState.currentValue) {
if (sheetState.currentValue == SheetValue.Expanded) {
expanded = true
}
}
val context = LocalContext.current
val settingsRequest = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
context.findActivity()?.recreate()
}
ModalBottomSheet(
sheetState = sheetState,
containerColor = MaterialTheme.colors.surface,
onDismissRequest = { dismiss() }
) {
val scope = rememberCoroutineScope()
TaskListDrawer(
bottomPadding = bottomPadding,
begForMoney = begForMoney,
filters = items,
onClick = {
when (it) {
is DrawerItem.Filter -> {
setFilter(it.type())
scope.launch(Dispatchers.Default) {
sheetState.hide()
dismiss()
}
}
is DrawerItem.Header -> {
toggleCollapsed(it.type())
}
}
},
onAddClick = {
scope.launch(Dispatchers.Default) {
sheetState.hide()
dismiss()
addFilter(it.type())
}
},
onDrawerAction = {
dismiss()
when (it) {
DrawerAction.PURCHASE ->
if (Tasks.IS_GENERIC)
context.openUri(R.string.url_donate)
else
context.startActivity(Intent(context, PurchaseActivity::class.java))
DrawerAction.CUSTOMIZE_DRAWER ->
context.startActivity(
Intent(context, NavigationDrawerCustomization::class.java)
)
DrawerAction.SETTINGS ->
settingsRequest.launch(Intent(context, MainPreferences::class.java))
DrawerAction.HELP_AND_FEEDBACK ->
context.startActivity(Intent(context, HelpAndFeedback::class.java))
}
},
onErrorClick = {
context.startActivity(Intent(context, MainPreferences::class.java))
},
)
}
}

@ -1,9 +1,11 @@
package org.tasks.extensions package org.tasks.extensions
import android.app.Activity
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE
import android.content.ContextWrapper
import android.content.Intent import android.content.Intent
import android.content.Intent.ACTION_VIEW import android.content.Intent.ACTION_VIEW
import android.content.res.Configuration import android.content.res.Configuration
@ -92,4 +94,13 @@ object Context {
false false
} }
} }
fun Context.findActivity(): Activity? {
var context = this
while (context is ContextWrapper) {
if (context is Activity) return context
context = context.baseContext
}
return null
}
} }

Loading…
Cancel
Save