diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
index 5c90e4f..1780218 100644
--- a/.idea/caches/deviceStreaming.xml
+++ b/.idea/caches/deviceStreaming.xml
@@ -160,6 +160,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt
index 669ffbe..e735dc2 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MainScreen.kt
@@ -8,8 +8,13 @@ import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
@@ -24,6 +29,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.PermanentNavigationDrawer
import androidx.compose.material3.Scaffold
@@ -35,7 +41,9 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.VerticalDivider
import androidx.compose.material3.rememberDrawerState
+import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
@@ -50,6 +58,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavHostController
import androidx.navigation.NavType
@@ -79,33 +88,90 @@ fun MainScreen(
return
}
- if (viewModel.showTaskSheet) {
- TaskBottomSheet { viewModel.onDismissTaskSheet() }
- }
- if (viewModel.showAddListSheet) {
- AddListBottomSheet { viewModel.showAddListSheet = false }
- }
-
val navBackStackEntry by navController.currentBackStackEntryAsState()
viewModel.setCurrentDestination(navBackStackEntry)
val isExpandedScreen = windowSizeClass.widthSizeClass >= WindowWidthSizeClass.Medium
val orientation = LocalConfiguration.current.orientation
val isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE
- val showPermanentDrawer = isExpandedScreen || isLandscape
+ val isLargeLayout = isExpandedScreen || isLandscape
- if (showPermanentDrawer) {
+ if (isLargeLayout) {
PermanentNavigationDrawer(
drawerContent = {
- MenuScreen(currentDestination = viewModel.currentDestination)
+ MenuScreen(
+ modifier = Modifier.width(240.dp),
+ currentDestination = viewModel.currentDestination
+ )
}
) {
- AppContent(
- viewModel = viewModel,
- navController = navController
- )
+ Row(Modifier.fillMaxSize()) {
+ // Main app content area
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ ) {
+ AppContent(
+ viewModel = viewModel,
+ navController = navController
+ )
+ }
+
+ // Show side "details" pane for the task editor when requested
+ if (viewModel.showTaskSheet) {
+ VerticalDivider(
+ modifier = Modifier
+ .fillMaxHeight()
+ .width(1.dp)
+ )
+ Box(
+ modifier = Modifier
+ .width(380.dp)
+ .fillMaxHeight()
+ .background(MaterialTheme.colorScheme.surfaceContainerHigh)
+ ) {
+ TaskScreen { viewModel.onDismissTaskSheet() }
+ }
+ }
+ if (viewModel.showAddListSheet) {
+ Dialog(onDismissRequest = { viewModel.showAddListSheet = false }) {
+ Surface(
+ shape = RoundedCornerShape(16.dp),
+ tonalElevation = 6.dp,
+ modifier = Modifier
+ .widthIn(max = 400.dp)
+ .wrapContentHeight()
+ .padding(16.dp)
+ ) {
+ AddListScreen { viewModel.showAddListSheet = false }
+ }
+ }
+ }
+ }
}
} else {
+ if (viewModel.showTaskSheet) {
+ val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
+ val scope = rememberCoroutineScope()
+
+ ModalBottomSheet(
+ onDismissRequest = {
+ scope.launch {
+ sheetState.hide()
+ viewModel.onDismissTaskSheet()
+ }
+ },
+ sheetState = sheetState) {
+ TaskScreen { viewModel.onDismissTaskSheet() }
+ }
+ }
+ if (viewModel.showAddListSheet) {
+ ModalBottomSheet(onDismissRequest = { viewModel.showAddListSheet = false }) {
+ AddListScreen { viewModel.showAddListSheet = false }
+ }
+ }
+
val drawerState = rememberDrawerState(DrawerValue.Closed)
ModalNavigationDrawer(
drawerContent = {
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/ManageListsScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/ManageListsScreen.kt
index ea1cb64..d6ed644 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/ManageListsScreen.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/ManageListsScreen.kt
@@ -32,7 +32,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -217,7 +216,7 @@ fun ManageListsScreen(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun AddListBottomSheet(
+fun AddListScreen(
viewModel: ManageListsViewModel = hiltViewModel(),
onDismiss: () -> Unit
) {
@@ -227,35 +226,34 @@ fun AddListBottomSheet(
titleFocusRequester.requestFocus()
}
- ModalBottomSheet(onDismissRequest = onDismiss) {
- var name by remember { mutableStateOf("") }
- //var type by remember { mutableStateOf(ListType.Default) }
- //var description by remember { mutableStateOf("") }
+ var name by remember { mutableStateOf("") }
+ //var type by remember { mutableStateOf(ListType.Default) }
+ //var description by remember { mutableStateOf("") }
- Column(modifier = Modifier.padding(16.dp)) {
- Text("New List", style = MaterialTheme.typography.titleMedium)
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text("New List", style = MaterialTheme.typography.titleMedium)
- Spacer(Modifier.height(8.dp))
- /*TextField(
+ Spacer(Modifier.height(8.dp))
+ /*TextField(
value = name,
onValueChange = { name = it },
label = { Text("List Name") },
singleLine = true
)*/
- OutlinedTextField(
- value = name,
- singleLine = true,
- onValueChange = { name = it },
- label = { Text("Title") },
- modifier = Modifier
- .fillMaxWidth()
- .focusRequester(titleFocusRequester)
- )
+ OutlinedTextField(
+ value = name,
+ singleLine = true,
+ onValueChange = { name = it },
+ label = { Text("Title") },
+ modifier = Modifier
+ .fillMaxWidth()
+ .focusRequester(titleFocusRequester)
+ )
- Spacer(Modifier.height(8.dp))
- //DropdownSelector(selected = type, onSelect = { type = it })
+ Spacer(Modifier.height(8.dp))
+ //DropdownSelector(selected = type, onSelect = { type = it })
- /*Spacer(Modifier.height(8.dp))
+ /*Spacer(Modifier.height(8.dp))
TextField(
value = description,
onValueChange = { description = it },
@@ -263,19 +261,18 @@ fun AddListBottomSheet(
maxLines = 3
)*/
- Spacer(Modifier.height(16.dp))
- Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
- //TextButton(onClick = onDismiss) { Text("Cancel") }
- //Spacer(Modifier.width(8.dp))
- Button(
- onClick = {
- viewModel.createTaskList(name/*, type, description*/, viewModel.taskCount + 1)
- onDismiss()
- },
- enabled = name.isNotBlank()
- ) {
- Text("Create")
- }
+ Spacer(Modifier.height(16.dp))
+ Row(horizontalArrangement = Arrangement.End, modifier = Modifier.fillMaxWidth()) {
+ //TextButton(onClick = onDismiss) { Text("Cancel") }
+ //Spacer(Modifier.width(8.dp))
+ Button(
+ onClick = {
+ viewModel.createTaskList(name/*, type, description*/, viewModel.taskCount + 1)
+ onDismiss()
+ },
+ enabled = name.isNotBlank()
+ ) {
+ Text("Create")
}
}
}
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MenuScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MenuScreen.kt
index 2301fba..ce81c44 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MenuScreen.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/MenuScreen.kt
@@ -29,10 +29,12 @@ import com.wismna.geoffroy.donext.presentation.viewmodel.MenuViewModel
@Composable
fun MenuScreen(
+ modifier: Modifier = Modifier,
viewModel: MenuViewModel = hiltViewModel(),
currentDestination: AppDestination,
) {
ModalDrawerSheet(
+ modifier = modifier,
drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant,
drawerContentColor = MaterialTheme.colorScheme.onSurfaceVariant
) {
diff --git a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt
index be3814c..b791f02 100644
--- a/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt
+++ b/donextv2/src/main/java/com/wismna/geoffroy/donext/presentation/screen/TaskScreen.kt
@@ -18,7 +18,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
@@ -27,13 +26,11 @@ import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberDatePickerState
-import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -44,7 +41,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.wismna.geoffroy.donext.domain.extension.toLocalDate
import com.wismna.geoffroy.donext.domain.model.Priority
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
-import kotlinx.coroutines.launch
import java.time.LocalDate
import java.time.ZoneId
import java.time.ZoneOffset
@@ -53,159 +49,149 @@ import java.time.format.FormatStyle
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun TaskBottomSheet(
+fun TaskScreen(
viewModel: TaskViewModel = hiltViewModel(),
onDismiss: () -> Unit
) {
val titleFocusRequester = remember { FocusRequester() }
- val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
- val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
titleFocusRequester.requestFocus()
}
- ModalBottomSheet(
- onDismissRequest = onDismiss,
- sheetState = sheetState) {
- Column(Modifier.padding(16.dp)) {
- Text(
- viewModel.screenTitle(),
- style = MaterialTheme.typography.titleLarge
+ Column(Modifier.padding(16.dp)) {
+ Text(
+ viewModel.screenTitle(),
+ style = MaterialTheme.typography.titleLarge
+ )
+ Spacer(Modifier.height(8.dp))
+
+ // --- Title ---
+ OutlinedTextField(
+ value = viewModel.title,
+ singleLine = true,
+ readOnly = viewModel.isDeleted,
+ onValueChange = { viewModel.onTitleChanged(it) },
+ label = { Text("Title") },
+ modifier = Modifier
+ .fillMaxWidth()
+ .focusRequester(titleFocusRequester)
+ )
+ Spacer(Modifier.height(12.dp))
+
+ // --- Description ---
+ OutlinedTextField(
+ value = viewModel.description,
+ readOnly = viewModel.isDeleted,
+ onValueChange = { viewModel.onDescriptionChanged(it) },
+ label = { Text("Description") },
+ maxLines = 3,
+ modifier = Modifier.fillMaxWidth()
+ )
+ Spacer(Modifier.height(12.dp))
+
+ // --- Priority ---
+ Row(
+ modifier = Modifier.fillMaxWidth().padding(start = 17.dp),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text("Priority", style = MaterialTheme.typography.labelLarge)
+ SingleChoiceSegmentedButton(
+ value = viewModel.priority,
+ isEnabled = !viewModel.isDeleted,
+ onValueChange = { viewModel.onPriorityChanged(it) }
)
- Spacer(Modifier.height(8.dp))
+ }
+ Spacer(Modifier.height(12.dp))
- // --- Title ---
- OutlinedTextField(
- value = viewModel.title,
- singleLine = true,
- readOnly = viewModel.isDeleted,
- onValueChange = { viewModel.onTitleChanged(it) },
- label = { Text("Title") },
- modifier = Modifier
- .fillMaxWidth()
- .focusRequester(titleFocusRequester)
- )
- Spacer(Modifier.height(12.dp))
+ // --- Due Date ---
+ var showDatePicker by remember { mutableStateOf(false) }
+ val formattedDate = viewModel.dueDate?.toLocalDate()?.format(
+ DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)
+ )
+ ?: ""
- // --- Description ---
- OutlinedTextField(
- value = viewModel.description,
- readOnly = viewModel.isDeleted,
- onValueChange = { viewModel.onDescriptionChanged(it) },
- label = { Text("Description") },
- maxLines = 3,
- modifier = Modifier.fillMaxWidth()
- )
- Spacer(Modifier.height(12.dp))
-
- // --- Priority ---
- Row (
- modifier = Modifier.fillMaxWidth().padding(start = 17.dp),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically) {
- Text("Priority", style = MaterialTheme.typography.labelLarge)
- SingleChoiceSegmentedButton(
- value = viewModel.priority,
- isEnabled = !viewModel.isDeleted,
- onValueChange = { viewModel.onPriorityChanged(it) }
- )
- }
- Spacer(Modifier.height(12.dp))
-
- // --- Due Date ---
- var showDatePicker by remember { mutableStateOf(false) }
- val formattedDate = viewModel.dueDate?.toLocalDate()?.format(
- DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM))
- ?: ""
-
- OutlinedTextField(
- value = formattedDate,
- onValueChange = {},
- readOnly = true,
- label = { Text("Due Date") },
- trailingIcon = {
- Row {
- if (viewModel.dueDate != null) {
- IconButton(
- onClick = { viewModel.onDueDateChanged(null) },
- enabled = !viewModel.isDeleted) {
- Icon(Icons.Default.Clear, contentDescription = "Clear due date")
- }
- }
+ OutlinedTextField(
+ value = formattedDate,
+ onValueChange = {},
+ readOnly = true,
+ label = { Text("Due Date") },
+ trailingIcon = {
+ Row {
+ if (viewModel.dueDate != null) {
IconButton(
- onClick = { showDatePicker = true },
- enabled = !viewModel.isDeleted) {
- Icon(Icons.Default.CalendarMonth, contentDescription = "Pick due date")
+ onClick = { viewModel.onDueDateChanged(null) },
+ enabled = !viewModel.isDeleted
+ ) {
+ Icon(Icons.Default.Clear, contentDescription = "Clear due date")
}
}
- },
- modifier = Modifier.fillMaxWidth()
+ IconButton(
+ onClick = { showDatePicker = true },
+ enabled = !viewModel.isDeleted
+ ) {
+ Icon(Icons.Default.CalendarMonth, contentDescription = "Pick due date")
+ }
+ }
+ },
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ if (showDatePicker) {
+ val datePickerState = rememberDatePickerState(
+ initialSelectedDateMillis = viewModel.dueDate,
+ selectableDates = object : SelectableDates {
+ override fun isSelectableDate(utcTimeMillis: Long): Boolean {
+ val todayStartUtcMillis = LocalDate.now(ZoneId.systemDefault())
+ .atStartOfDay(ZoneOffset.UTC)
+ .toInstant()
+ .toEpochMilli()
+ return utcTimeMillis >= todayStartUtcMillis
+ }
+ }
)
- if (showDatePicker) {
- val datePickerState = rememberDatePickerState(
- initialSelectedDateMillis = viewModel.dueDate,
- selectableDates = object: SelectableDates {
- override fun isSelectableDate(utcTimeMillis: Long): Boolean {
- val todayStartUtcMillis = LocalDate.now(ZoneId.systemDefault())
- .atStartOfDay(ZoneOffset.UTC)
- .toInstant()
- .toEpochMilli()
- return utcTimeMillis >= todayStartUtcMillis
- }
- }
- )
-
- DatePickerDialog(
- onDismissRequest = { showDatePicker = false },
- confirmButton = {
- TextButton(
- onClick = {
+ DatePickerDialog(
+ onDismissRequest = { showDatePicker = false },
+ confirmButton = {
+ TextButton(
+ onClick = {
datePickerState.selectedDateMillis?.let { viewModel.onDueDateChanged(it) }
showDatePicker = false
}) { Text("OK") }
- },
- dismissButton = {
- TextButton(onClick = { showDatePicker = false }) { Text("Cancel") }
- }
- ) {
- DatePicker(state = datePickerState)
+ },
+ dismissButton = {
+ TextButton(onClick = { showDatePicker = false }) { Text("Cancel") }
}
+ ) {
+ DatePicker(state = datePickerState)
}
- if (!viewModel.isDeleted) {
- Spacer(Modifier.height(16.dp))
+ }
+ if (!viewModel.isDeleted) {
+ Spacer(Modifier.height(16.dp))
- Row (
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ // --- Cancel Button ---
+ Button(
+ onClick = { onDismiss() },
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.primary
+ )
+ ) { Text("Cancel") }
+
+ // --- Save Button ---
+ Button(
+ onClick = {
+ viewModel.save()
+ onDismiss()
+ },
+ enabled = viewModel.title.isNotBlank() && !viewModel.isDeleted,
) {
- // --- Cancel Button ---
- Button(
- onClick = {
- scope.launch {
- sheetState.hide()
- onDismiss()
- }
- },
- colors = ButtonDefaults.textButtonColors(
- contentColor = MaterialTheme.colorScheme.primary
- )
- ) { Text("Cancel") }
-
- // --- Save Button ---
- Button(
- onClick = {
- scope.launch {
- viewModel.save()
- sheetState.hide()
- onDismiss()
- }
- },
- enabled = viewModel.title.isNotBlank() && !viewModel.isDeleted,
- ) {
- Text(if (viewModel.isEditing()) "Save" else "Create")
- }
+ Text(if (viewModel.isEditing()) "Save" else "Create")
}
}
}