mirror of https://github.com/tasks/tasks
Add navigation and HomeScreenDestination
parent
e8b8fc0c87
commit
d3fed98e64
@ -0,0 +1,6 @@
|
|||||||
|
package org.tasks.compose
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object HomeDestination
|
||||||
@ -0,0 +1,273 @@
|
|||||||
|
package org.tasks.compose.home
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.ime
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.systemBars
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
|
import androidx.compose.material3.ModalNavigationDrawer
|
||||||
|
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
|
||||||
|
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
|
||||||
|
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
|
||||||
|
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
|
||||||
|
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.fragment.compose.AndroidFragment
|
||||||
|
import androidx.fragment.compose.rememberFragmentState
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.todoroo.astrid.activity.MainActivityViewModel
|
||||||
|
import com.todoroo.astrid.activity.TaskEditFragment
|
||||||
|
import com.todoroo.astrid.activity.TaskEditFragment.Companion.EXTRA_TASK
|
||||||
|
import com.todoroo.astrid.activity.TaskListFragment
|
||||||
|
import com.todoroo.astrid.activity.TaskListFragment.Companion.EXTRA_FILTER
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.tasks.R
|
||||||
|
import org.tasks.TasksApplication
|
||||||
|
import org.tasks.activities.TagSettingsActivity
|
||||||
|
import org.tasks.billing.PurchaseActivity
|
||||||
|
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.listSettingsClass
|
||||||
|
import org.tasks.extensions.Context.openUri
|
||||||
|
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.kmp.org.tasks.compose.TouchSlopMultiplier
|
||||||
|
import org.tasks.kmp.org.tasks.compose.rememberImeState
|
||||||
|
import org.tasks.location.LocationPickerActivity
|
||||||
|
import org.tasks.preferences.HelpAndFeedback
|
||||||
|
import org.tasks.preferences.MainPreferences
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun HomeScreen(
|
||||||
|
viewModel: MainActivityViewModel = hiltViewModel(),
|
||||||
|
newList: suspend (Class<out Activity>) -> Unit,
|
||||||
|
state: MainActivityViewModel.State,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
showNewFilterDialog: () -> Unit,
|
||||||
|
navigator: ThreePaneScaffoldNavigator<Any>,
|
||||||
|
) {
|
||||||
|
val currentWindowInsets = WindowInsets.systemBars.asPaddingValues()
|
||||||
|
val windowInsets = remember { mutableStateOf(currentWindowInsets) }
|
||||||
|
val keyboard = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
|
LaunchedEffect(currentWindowInsets) {
|
||||||
|
Timber.d("insets: $currentWindowInsets")
|
||||||
|
if (currentWindowInsets.calculateTopPadding() != 0.dp || currentWindowInsets.calculateBottomPadding() != 0.dp) {
|
||||||
|
windowInsets.value = currentWindowInsets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val isListVisible =
|
||||||
|
navigator.scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded
|
||||||
|
val isDetailVisible =
|
||||||
|
navigator.scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded
|
||||||
|
|
||||||
|
TouchSlopMultiplier {
|
||||||
|
ModalNavigationDrawer(
|
||||||
|
drawerState = drawerState,
|
||||||
|
gesturesEnabled = isListVisible,
|
||||||
|
drawerContent = {
|
||||||
|
ModalDrawerSheet(
|
||||||
|
drawerState = drawerState,
|
||||||
|
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
TaskListDrawer(
|
||||||
|
arrangement = if (state.menuQuery.isBlank()) Arrangement.Top else Arrangement.Bottom,
|
||||||
|
filters = if (state.menuQuery.isNotEmpty()) state.searchItems else state.drawerItems,
|
||||||
|
onClick = {
|
||||||
|
when (it) {
|
||||||
|
is DrawerItem.Filter -> {
|
||||||
|
viewModel.setFilter(it.filter)
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
keyboard?.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is DrawerItem.Header -> {
|
||||||
|
viewModel.toggleCollapsed(it.header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAddClick = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
when (it.header.addIntentRc) {
|
||||||
|
FilterProvider.REQUEST_NEW_FILTER ->
|
||||||
|
showNewFilterDialog()
|
||||||
|
|
||||||
|
REQUEST_NEW_PLACE ->
|
||||||
|
newList(LocationPickerActivity::class.java)
|
||||||
|
|
||||||
|
REQUEST_NEW_TAGS ->
|
||||||
|
newList(TagSettingsActivity::class.java)
|
||||||
|
|
||||||
|
REQUEST_NEW_LIST ->
|
||||||
|
when (it.header.subheaderType) {
|
||||||
|
NavigationDrawerSubheader.SubheaderType.CALDAV,
|
||||||
|
NavigationDrawerSubheader.SubheaderType.TASKS ->
|
||||||
|
viewModel
|
||||||
|
.getAccount(it.header.id.toLong())
|
||||||
|
?.let { newList(it.listSettingsClass()) }
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> Timber.e("Unhandled request code: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onErrorClick = {
|
||||||
|
context.startActivity(Intent(context, MainPreferences::class.java))
|
||||||
|
},
|
||||||
|
searchBar = {
|
||||||
|
MenuSearchBar(
|
||||||
|
begForMoney = state.begForMoney,
|
||||||
|
onDrawerAction = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
when (it) {
|
||||||
|
DrawerAction.PURCHASE ->
|
||||||
|
if (TasksApplication.IS_GENERIC)
|
||||||
|
context.openUri(R.string.url_donate)
|
||||||
|
else
|
||||||
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
PurchaseActivity::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
DrawerAction.HELP_AND_FEEDBACK ->
|
||||||
|
context.startActivity(
|
||||||
|
Intent(
|
||||||
|
context,
|
||||||
|
HelpAndFeedback::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
query = state.menuQuery,
|
||||||
|
onQueryChange = { viewModel.queryMenu(it) },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
ListDetailPaneScaffold(
|
||||||
|
directive = navigator.scaffoldDirective,
|
||||||
|
value = navigator.scaffoldValue,
|
||||||
|
listPane = {
|
||||||
|
key (state.filter) {
|
||||||
|
val fragment = remember { mutableStateOf<TaskListFragment?>(null) }
|
||||||
|
val keyboardOpen = rememberImeState()
|
||||||
|
AndroidFragment<TaskListFragment>(
|
||||||
|
fragmentState = rememberFragmentState(),
|
||||||
|
arguments = remember(state.filter) {
|
||||||
|
Bundle()
|
||||||
|
.apply { putParcelable(EXTRA_FILTER, state.filter) }
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.imePadding(),
|
||||||
|
) { tlf ->
|
||||||
|
fragment.value = tlf
|
||||||
|
tlf.applyInsets(windowInsets.value)
|
||||||
|
tlf.setNavigationClickListener {
|
||||||
|
scope.launch { drawerState.open() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(fragment, windowInsets, keyboardOpen.value) {
|
||||||
|
fragment.value?.applyInsets(
|
||||||
|
if (keyboardOpen.value) {
|
||||||
|
PaddingValues(
|
||||||
|
top = windowInsets.value.calculateTopPadding(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
windowInsets.value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
detailPane = {
|
||||||
|
val direction = LocalLayoutDirection.current
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
top = windowInsets.value.calculateTopPadding(),
|
||||||
|
start = windowInsets.value.calculateStartPadding(direction),
|
||||||
|
end = windowInsets.value.calculateEndPadding(direction),
|
||||||
|
bottom = if (rememberImeState().value)
|
||||||
|
WindowInsets.ime.asPaddingValues().calculateBottomPadding()
|
||||||
|
else
|
||||||
|
windowInsets.value.calculateBottomPadding()
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
if (state.task == null) {
|
||||||
|
if (isListVisible && isDetailVisible) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(org.tasks.kmp.R.drawable.ic_launcher_no_shadow_foreground),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(192.dp),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
key(state.task) {
|
||||||
|
AndroidFragment<TaskEditFragment>(
|
||||||
|
fragmentState = rememberFragmentState(),
|
||||||
|
arguments = remember(state.task) {
|
||||||
|
Bundle()
|
||||||
|
.apply { putParcelable(EXTRA_TASK, state.task) }
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue