mirror of
https://github.com/wismna/DoNext.git
synced 2025-12-06 00:02:40 -05:00
Compare commits
3 Commits
7dddc62377
...
e07f389fac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e07f389fac | ||
|
|
313e514624 | ||
|
|
8e78f9b464 |
48
.idea/caches/deviceStreaming.xml
generated
48
.idea/caches/deviceStreaming.xml
generated
@@ -232,6 +232,18 @@
|
|||||||
<option name="screenX" value="1080" />
|
<option name="screenX" value="1080" />
|
||||||
<option name="screenY" value="2640" />
|
<option name="screenY" value="2640" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="36" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="blazer" />
|
||||||
|
<option name="id" value="blazer" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 10 Pro" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2410" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="32" />
|
<option name="api" value="32" />
|
||||||
<option name="brand" value="google" />
|
<option name="brand" value="google" />
|
||||||
@@ -439,6 +451,18 @@
|
|||||||
<option name="screenX" value="720" />
|
<option name="screenX" value="720" />
|
||||||
<option name="screenY" value="1600" />
|
<option name="screenY" value="1600" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="36" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="frankel" />
|
||||||
|
<option name="id" value="frankel" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 10" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2424" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="34" />
|
<option name="api" value="34" />
|
||||||
<option name="brand" value="samsung" />
|
<option name="brand" value="samsung" />
|
||||||
@@ -657,6 +681,18 @@
|
|||||||
<option name="screenX" value="720" />
|
<option name="screenX" value="720" />
|
||||||
<option name="screenY" value="1600" />
|
<option name="screenY" value="1600" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="36" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="mustang" />
|
||||||
|
<option name="id" value="mustang" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 10 Pro XL" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2404" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="34" />
|
<option name="api" value="34" />
|
||||||
<option name="brand" value="samsung" />
|
<option name="brand" value="samsung" />
|
||||||
@@ -755,6 +791,18 @@
|
|||||||
<option name="screenX" value="1080" />
|
<option name="screenX" value="1080" />
|
||||||
<option name="screenY" value="2340" />
|
<option name="screenY" value="2340" />
|
||||||
</PersistentDeviceSelectionData>
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="36" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="rango" />
|
||||||
|
<option name="id" value="rango" />
|
||||||
|
<option name="labId" value="google" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 10 Pro Fold" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="2076" />
|
||||||
|
<option name="screenY" value="2152" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
<PersistentDeviceSelectionData>
|
<PersistentDeviceSelectionData>
|
||||||
<option name="api" value="30" />
|
<option name="api" value="30" />
|
||||||
<option name="brand" value="google" />
|
<option name="brand" value="google" />
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.wismna.geoffroy.donext.domain.model.Task
|
import com.wismna.geoffroy.donext.domain.model.Task
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.DueTodayViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.DueTodayViewModel
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DueTodayTasksScreen(
|
fun DueTodayTasksScreen(
|
||||||
@@ -41,7 +40,7 @@ fun DueTodayTasksScreen(
|
|||||||
items(tasks, key = { it.id!! }) { task ->
|
items(tasks, key = { it.id!! }) { task ->
|
||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
viewModel = TaskItemViewModel(task),
|
task = task,
|
||||||
onTaskClick = { onTaskClick(task) },
|
onTaskClick = { onTaskClick(task) },
|
||||||
onSwipeLeft = {
|
onSwipeLeft = {
|
||||||
viewModel.updateTaskDone(task.id!!)
|
viewModel.updateTaskDone(task.id!!)
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import androidx.compose.material3.CircularProgressIndicator
|
|||||||
import androidx.compose.material3.DrawerState
|
import androidx.compose.material3.DrawerState
|
||||||
import androidx.compose.material3.DrawerValue
|
import androidx.compose.material3.DrawerValue
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
@@ -31,7 +32,10 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.material3.rememberDrawerState
|
import androidx.compose.material3.rememberDrawerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -46,22 +50,20 @@ import androidx.navigation.compose.currentBackStackEntryAsState
|
|||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.wismna.geoffroy.donext.domain.model.AppDestination
|
import com.wismna.geoffroy.donext.domain.model.AppDestination
|
||||||
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.MainViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.MainViewModel
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
viewModel: MainViewModel = hiltViewModel()
|
viewModel: MainViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
// TODO: find a way to get rid of this
|
|
||||||
val taskViewModel: TaskViewModel = hiltViewModel()
|
|
||||||
|
|
||||||
if (viewModel.isLoading) {
|
if (viewModel.isLoading) {
|
||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
@@ -71,7 +73,7 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (viewModel.showTaskSheet) {
|
if (viewModel.showTaskSheet) {
|
||||||
TaskBottomSheet(taskViewModel) { viewModel.showTaskSheet = false }
|
TaskBottomSheet { viewModel.onDismissTaskSheet() }
|
||||||
}
|
}
|
||||||
if (viewModel.showAddListSheet) {
|
if (viewModel.showAddListSheet) {
|
||||||
AddListBottomSheet { viewModel.showAddListSheet = false }
|
AddListBottomSheet { viewModel.showAddListSheet = false }
|
||||||
@@ -82,21 +84,11 @@ fun MainScreen(
|
|||||||
|
|
||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
MenuScreen (
|
MenuScreen (currentDestination = viewModel.currentDestination)
|
||||||
currentDestination = viewModel.currentDestination,
|
|
||||||
onNavigate = { route ->
|
|
||||||
scope.launch {
|
|
||||||
drawerState.close()
|
|
||||||
navController.navigate(route) {
|
|
||||||
restoreState = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
drawerState = drawerState
|
drawerState = drawerState
|
||||||
) {
|
) {
|
||||||
AppContent(viewModel = viewModel, taskViewModel = taskViewModel, navController = navController, scope = scope, drawerState = drawerState)
|
AppContent(viewModel = viewModel, navController = navController, scope = scope, drawerState = drawerState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,11 +96,22 @@ fun MainScreen(
|
|||||||
fun AppContent(
|
fun AppContent(
|
||||||
modifier : Modifier = Modifier,
|
modifier : Modifier = Modifier,
|
||||||
viewModel: MainViewModel,
|
viewModel: MainViewModel,
|
||||||
taskViewModel: TaskViewModel,
|
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
drawerState: DrawerState
|
drawerState: DrawerState
|
||||||
) {
|
) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.uiEventBus.events.collectLatest { event ->
|
||||||
|
when (event) {
|
||||||
|
is UiEvent.Navigate -> {
|
||||||
|
drawerState.close()
|
||||||
|
navController.navigate(event.route)
|
||||||
|
}
|
||||||
|
is UiEvent.NavigateBack -> navController.popBackStack()
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier.background(MaterialTheme.colorScheme.primaryContainer),
|
modifier = modifier.background(MaterialTheme.colorScheme.primaryContainer),
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
@@ -123,7 +126,7 @@ fun AppContent(
|
|||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onPrimaryContainer) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onPrimaryContainer) {
|
||||||
if (viewModel.currentDestination.showBackButton) {
|
if (viewModel.currentDestination.showBackButton) {
|
||||||
IconButton(onClick = { navController.popBackStack() }) {
|
IconButton(onClick = { viewModel.navigateBack() }) {
|
||||||
Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
|
Icon(Icons.AutoMirrored.Default.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -154,9 +157,10 @@ fun AppContent(
|
|||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
when (val dest = viewModel.currentDestination) {
|
when (val dest = viewModel.currentDestination) {
|
||||||
is AppDestination.TaskList -> {
|
is AppDestination.TaskList -> {
|
||||||
TaskListFab(
|
ExtendedFloatingActionButton(
|
||||||
taskListId = dest.taskListId,
|
onClick = { viewModel.onNewTaskButtonClicked(dest.taskListId) },
|
||||||
showBottomSheet = { viewModel.showTaskSheet = it }
|
icon = { Icon(Icons.Filled.Add, "Create a task.") },
|
||||||
|
text = { Text("Create a task") },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
@@ -192,14 +196,20 @@ fun AppContent(
|
|||||||
type = NavType.LongType
|
type = NavType.LongType
|
||||||
})
|
})
|
||||||
) { navBackStackEntry ->
|
) { navBackStackEntry ->
|
||||||
// TODO: when task list has been deleted, we should not navigate to it event if in the stack
|
val taskListId = navBackStackEntry.arguments?.getLong("taskListId") ?: return@composable
|
||||||
|
val listExists by remember(taskListId, viewModel.destinations) {
|
||||||
|
derivedStateOf { viewModel.doesListExist(taskListId) }
|
||||||
|
}
|
||||||
|
LaunchedEffect(listExists) {
|
||||||
|
if (!viewModel.doesListExist(taskListId)) {
|
||||||
|
viewModel.navigateBack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val taskListViewModel: TaskListViewModel = hiltViewModel(navBackStackEntry)
|
val taskListViewModel: TaskListViewModel = hiltViewModel(navBackStackEntry)
|
||||||
TaskListScreen(
|
TaskListScreen(
|
||||||
viewModel = taskListViewModel,
|
viewModel = taskListViewModel,
|
||||||
onTaskClick = { task ->
|
onTaskClick = { task -> viewModel.onTaskClicked(task) }
|
||||||
taskViewModel.startEditTask(task)
|
|
||||||
viewModel.showTaskSheet = true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,19 +222,13 @@ fun AppContent(
|
|||||||
composable(AppDestination.DueTodayList.route) {
|
composable(AppDestination.DueTodayList.route) {
|
||||||
DueTodayTasksScreen (
|
DueTodayTasksScreen (
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
onTaskClick = { task ->
|
onTaskClick = { task -> viewModel.onTaskClicked(task) }
|
||||||
taskViewModel.startEditTask(task)
|
|
||||||
viewModel.showTaskSheet = true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(AppDestination.RecycleBin.route) {
|
composable(AppDestination.RecycleBin.route) {
|
||||||
RecycleBinScreen(
|
RecycleBinScreen(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
onTaskClick = { task ->
|
onTaskClick = { task -> viewModel.onTaskClicked(task) }
|
||||||
taskViewModel.startEditTask(task)
|
|
||||||
viewModel.showTaskSheet = true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import com.wismna.geoffroy.donext.presentation.viewmodel.MenuViewModel
|
|||||||
fun MenuScreen(
|
fun MenuScreen(
|
||||||
viewModel: MenuViewModel = hiltViewModel(),
|
viewModel: MenuViewModel = hiltViewModel(),
|
||||||
currentDestination: AppDestination,
|
currentDestination: AppDestination,
|
||||||
onNavigate: (String) -> Unit
|
|
||||||
) {
|
) {
|
||||||
ModalDrawerSheet(
|
ModalDrawerSheet(
|
||||||
drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
drawerContainerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||||
@@ -58,7 +57,7 @@ fun MenuScreen(
|
|||||||
},
|
},
|
||||||
icon = { Icon(Icons.Default.Today, contentDescription = "Due Today") },
|
icon = { Icon(Icons.Default.Today, contentDescription = "Due Today") },
|
||||||
selected = currentDestination is AppDestination.DueTodayList,
|
selected = currentDestination is AppDestination.DueTodayList,
|
||||||
onClick = { onNavigate(AppDestination.DueTodayList.route) },
|
onClick = { viewModel.navigateTo(AppDestination.DueTodayList.route) },
|
||||||
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
|
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
|
||||||
)
|
)
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
@@ -74,7 +73,7 @@ fun MenuScreen(
|
|||||||
icon = { Icon(Icons.Default.LineWeight, contentDescription = list.name) },
|
icon = { Icon(Icons.Default.LineWeight, contentDescription = list.name) },
|
||||||
selected = currentDestination is AppDestination.TaskList &&
|
selected = currentDestination is AppDestination.TaskList &&
|
||||||
currentDestination.taskListId == list.id,
|
currentDestination.taskListId == list.id,
|
||||||
onClick = { onNavigate("taskList/${list.id}") },
|
onClick = { viewModel.navigateTo("taskList/${list.id}") },
|
||||||
badge = {
|
badge = {
|
||||||
if (list.overdueCount > 0) {
|
if (list.overdueCount > 0) {
|
||||||
Badge { Text(list.overdueCount.toString()) }
|
Badge { Text(list.overdueCount.toString()) }
|
||||||
@@ -91,14 +90,14 @@ fun MenuScreen(
|
|||||||
label = { Text("Recycle Bin") },
|
label = { Text("Recycle Bin") },
|
||||||
icon = { Icon(Icons.Default.Delete, contentDescription = "Recycle Bin") },
|
icon = { Icon(Icons.Default.Delete, contentDescription = "Recycle Bin") },
|
||||||
selected = currentDestination is AppDestination.RecycleBin,
|
selected = currentDestination is AppDestination.RecycleBin,
|
||||||
onClick = { onNavigate(AppDestination.RecycleBin.route) },
|
onClick = { viewModel.navigateTo(AppDestination.RecycleBin.route) },
|
||||||
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
|
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
|
||||||
)
|
)
|
||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
label = { Text("Edit Lists") },
|
label = { Text("Edit Lists") },
|
||||||
icon = { Icon(Icons.Default.EditNote, contentDescription = "Edit Lists") },
|
icon = { Icon(Icons.Default.EditNote, contentDescription = "Edit Lists") },
|
||||||
selected = currentDestination is AppDestination.ManageLists,
|
selected = currentDestination is AppDestination.ManageLists,
|
||||||
onClick = { onNavigate(AppDestination.ManageLists.route) },
|
onClick = { viewModel.navigateTo(AppDestination.ManageLists.route) },
|
||||||
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
|
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.wismna.geoffroy.donext.domain.model.Task
|
import com.wismna.geoffroy.donext.domain.model.Task
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.RecycleBinViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.RecycleBinViewModel
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RecycleBinScreen(
|
fun RecycleBinScreen(
|
||||||
@@ -77,7 +76,7 @@ fun RecycleBinScreen(
|
|||||||
items(items, key = { it.task.id!! }) { item ->
|
items(items, key = { it.task.id!! }) { item ->
|
||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
viewModel = TaskItemViewModel(item.task),
|
task = item.task,
|
||||||
onTaskClick = { onTaskClick(item.task) },
|
onTaskClick = { onTaskClick(item.task) },
|
||||||
onSwipeLeft = {
|
onSwipeLeft = {
|
||||||
viewModel.restore(item.task.id!!)
|
viewModel.restore(item.task.id!!)
|
||||||
|
|||||||
@@ -38,16 +38,18 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.wismna.geoffroy.donext.domain.model.Priority
|
import com.wismna.geoffroy.donext.domain.model.Priority
|
||||||
|
import com.wismna.geoffroy.donext.domain.model.Task
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskItemScreen(
|
fun TaskItemScreen(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: TaskItemViewModel,
|
task: Task,
|
||||||
onTaskClick: (taskId: Long) -> Unit,
|
onTaskClick: (taskId: Long) -> Unit,
|
||||||
onSwipeLeft: () -> Unit,
|
onSwipeLeft: () -> Unit,
|
||||||
onSwipeRight: () -> Unit
|
onSwipeRight: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val viewModel = TaskItemViewModel(task)
|
||||||
// TODO: change this
|
// TODO: change this
|
||||||
val dismissState = rememberSwipeToDismissBoxState(
|
val dismissState = rememberSwipeToDismissBoxState(
|
||||||
confirmValueChange = {
|
confirmValueChange = {
|
||||||
|
|||||||
@@ -8,12 +8,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Add
|
|
||||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -21,13 +16,10 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.wismna.geoffroy.donext.domain.model.Task
|
import com.wismna.geoffroy.donext.domain.model.Task
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskItemViewModel
|
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskListViewModel
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskListScreen(
|
fun TaskListScreen(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
viewModel: TaskListViewModel = hiltViewModel<TaskListViewModel>(),
|
viewModel: TaskListViewModel = hiltViewModel<TaskListViewModel>(),
|
||||||
onTaskClick: (Task) -> Unit) {
|
onTaskClick: (Task) -> Unit) {
|
||||||
val tasks = viewModel.tasks
|
val tasks = viewModel.tasks
|
||||||
@@ -50,7 +42,7 @@ fun TaskListScreen(
|
|||||||
) { task ->
|
) { task ->
|
||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
viewModel = TaskItemViewModel(task),
|
task = task,
|
||||||
onTaskClick = { onTaskClick(task) },
|
onTaskClick = { onTaskClick(task) },
|
||||||
onSwipeLeft = {
|
onSwipeLeft = {
|
||||||
viewModel.updateTaskDone(task.id!!, true)
|
viewModel.updateTaskDone(task.id!!, true)
|
||||||
@@ -81,7 +73,7 @@ fun TaskListScreen(
|
|||||||
) { task ->
|
) { task ->
|
||||||
TaskItemScreen(
|
TaskItemScreen(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
viewModel = TaskItemViewModel(task),
|
task = task,
|
||||||
onTaskClick = { onTaskClick(task) },
|
onTaskClick = { onTaskClick(task) },
|
||||||
onSwipeLeft = {
|
onSwipeLeft = {
|
||||||
viewModel.updateTaskDone(task.id!!, false)
|
viewModel.updateTaskDone(task.id!!, false)
|
||||||
@@ -96,19 +88,3 @@ fun TaskListScreen(
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun TaskListFab(
|
|
||||||
taskListId: Long,
|
|
||||||
viewModel: TaskViewModel = hiltViewModel(),
|
|
||||||
showBottomSheet: (Boolean) -> Unit = {}
|
|
||||||
) {
|
|
||||||
ExtendedFloatingActionButton(
|
|
||||||
onClick = {
|
|
||||||
viewModel.startNewTask(taskListId)
|
|
||||||
showBottomSheet(true)
|
|
||||||
},
|
|
||||||
icon = { Icon(Icons.Filled.Add, "Create a task.") },
|
|
||||||
text = { Text("Create a task") },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -40,6 +40,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.wismna.geoffroy.donext.domain.extension.toLocalDate
|
import com.wismna.geoffroy.donext.domain.extension.toLocalDate
|
||||||
import com.wismna.geoffroy.donext.domain.model.Priority
|
import com.wismna.geoffroy.donext.domain.model.Priority
|
||||||
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
|
import com.wismna.geoffroy.donext.presentation.viewmodel.TaskViewModel
|
||||||
@@ -53,7 +54,7 @@ import java.time.format.FormatStyle
|
|||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TaskBottomSheet(
|
fun TaskBottomSheet(
|
||||||
viewModel: TaskViewModel,
|
viewModel: TaskViewModel = hiltViewModel(),
|
||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
val titleFocusRequester = remember { FocusRequester() }
|
val titleFocusRequester = remember { FocusRequester() }
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.wismna.geoffroy.donext.presentation.ui.events
|
||||||
|
|
||||||
|
import com.wismna.geoffroy.donext.domain.model.Task
|
||||||
|
|
||||||
|
sealed class UiEvent {
|
||||||
|
data class Navigate(val route: String) : UiEvent()
|
||||||
|
data object NavigateBack : UiEvent()
|
||||||
|
data class ShowSnackbar(val message: String) : UiEvent()
|
||||||
|
|
||||||
|
data class EditTask(val task: Task) : UiEvent()
|
||||||
|
data class CreateNewTask(val taskListId: Long) : UiEvent()
|
||||||
|
data object CloseTask : UiEvent()
|
||||||
|
}
|
||||||
@@ -7,15 +7,20 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
import com.wismna.geoffroy.donext.domain.model.AppDestination
|
import com.wismna.geoffroy.donext.domain.model.AppDestination
|
||||||
|
import com.wismna.geoffroy.donext.domain.model.Task
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsUseCase
|
||||||
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent
|
||||||
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEventBus
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MainViewModel @Inject constructor(
|
class MainViewModel @Inject constructor(
|
||||||
getTaskListsUseCase: GetTaskListsUseCase
|
getTaskListsUseCase: GetTaskListsUseCase,
|
||||||
|
val uiEventBus: UiEventBus
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var isLoading by mutableStateOf(true)
|
var isLoading by mutableStateOf(true)
|
||||||
@@ -50,6 +55,33 @@ class MainViewModel @Inject constructor(
|
|||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun navigateBack() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.NavigateBack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onNewTaskButtonClicked(taskLisId: Long) {
|
||||||
|
showTaskSheet = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.CreateNewTask(taskLisId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onTaskClicked(task: Task) {
|
||||||
|
showTaskSheet = true
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.EditTask(task))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDismissTaskSheet() {
|
||||||
|
showTaskSheet = false
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.CloseTask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setCurrentDestination(navBackStackEntry: NavBackStackEntry?) {
|
fun setCurrentDestination(navBackStackEntry: NavBackStackEntry?) {
|
||||||
val route = navBackStackEntry?.destination?.route
|
val route = navBackStackEntry?.destination?.route
|
||||||
val taskListId = navBackStackEntry?.arguments?.getLong("taskListId")
|
val taskListId = navBackStackEntry?.arguments?.getLong("taskListId")
|
||||||
@@ -61,4 +93,10 @@ class MainViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
} ?: startDestination
|
} ?: startDestination
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun doesListExist(taskListId: Long): Boolean {
|
||||||
|
return destinations.any { dest ->
|
||||||
|
dest is AppDestination.TaskList && dest.taskListId == taskListId
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,17 +9,20 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
|
import com.wismna.geoffroy.donext.domain.model.TaskListWithOverdue
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.GetDueTodayTasksUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetDueTodayTasksUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsWithOverdueUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.GetTaskListsWithOverdueUseCase
|
||||||
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent
|
||||||
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEventBus
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MenuViewModel @Inject constructor(
|
class MenuViewModel @Inject constructor(
|
||||||
getTaskListsWithOverdue: GetTaskListsWithOverdueUseCase,
|
getTaskListsWithOverdue: GetTaskListsWithOverdueUseCase,
|
||||||
getDueTodayTasks: GetDueTodayTasksUseCase
|
getDueTodayTasks: GetDueTodayTasksUseCase,
|
||||||
|
private val uiEventBus: UiEventBus
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var taskLists by mutableStateOf<List<TaskListWithOverdue>>(emptyList())
|
var taskLists by mutableStateOf<List<TaskListWithOverdue>>(emptyList())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -38,4 +41,10 @@ class MenuViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun navigateTo(route: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.send(UiEvent.Navigate(route))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class TaskListViewModel @Inject constructor(
|
class TaskListViewModel @Inject constructor(
|
||||||
getTasks: GetTasksForListUseCase,
|
getTasks: GetTasksForListUseCase,
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
private val toggleTaskDone: ToggleTaskDoneUseCase,
|
private val toggleTaskDone: ToggleTaskDoneUseCase,
|
||||||
private val toggleTaskDeleted: ToggleTaskDeletedUseCase,
|
private val toggleTaskDeleted: ToggleTaskDeletedUseCase,
|
||||||
savedStateHandle: SavedStateHandle
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var tasks by mutableStateOf<List<Task>>(emptyList())
|
var tasks by mutableStateOf<List<Task>>(emptyList())
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import com.wismna.geoffroy.donext.domain.model.Priority
|
|||||||
import com.wismna.geoffroy.donext.domain.model.Task
|
import com.wismna.geoffroy.donext.domain.model.Task
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.AddTaskUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.AddTaskUseCase
|
||||||
import com.wismna.geoffroy.donext.domain.usecase.UpdateTaskUseCase
|
import com.wismna.geoffroy.donext.domain.usecase.UpdateTaskUseCase
|
||||||
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEvent
|
||||||
|
import com.wismna.geoffroy.donext.presentation.ui.events.UiEventBus
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
@@ -19,7 +21,8 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class TaskViewModel @Inject constructor(
|
class TaskViewModel @Inject constructor(
|
||||||
private val createTaskUseCase: AddTaskUseCase,
|
private val createTaskUseCase: AddTaskUseCase,
|
||||||
private val updateTaskUseCase: UpdateTaskUseCase
|
private val updateTaskUseCase: UpdateTaskUseCase,
|
||||||
|
private val uiEventBus: UiEventBus
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
var title by mutableStateOf("")
|
var title by mutableStateOf("")
|
||||||
@@ -38,10 +41,23 @@ class TaskViewModel @Inject constructor(
|
|||||||
private var editingTaskId: Long? = null
|
private var editingTaskId: Long? = null
|
||||||
private var taskListId: Long? = null
|
private var taskListId: Long? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
uiEventBus.events.collect { event ->
|
||||||
|
when (event) {
|
||||||
|
is UiEvent.EditTask -> startEditTask(event.task)
|
||||||
|
is UiEvent.CreateNewTask -> startNewTask(event.taskListId)
|
||||||
|
is UiEvent.CloseTask -> reset()
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun screenTitle(): String = if (isDeleted) "Task details" else if (isEditing()) "Edit Task" else "New Task"
|
fun screenTitle(): String = if (isDeleted) "Task details" else if (isEditing()) "Edit Task" else "New Task"
|
||||||
fun isEditing(): Boolean = editingTaskId != null
|
fun isEditing(): Boolean = editingTaskId != null
|
||||||
|
|
||||||
fun startNewTask(selectedListId: Long) {
|
private fun startNewTask(selectedListId: Long) {
|
||||||
editingTaskId = null
|
editingTaskId = null
|
||||||
taskListId = selectedListId
|
taskListId = selectedListId
|
||||||
title = ""
|
title = ""
|
||||||
@@ -51,7 +67,7 @@ class TaskViewModel @Inject constructor(
|
|||||||
isDeleted = false
|
isDeleted = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startEditTask(task: Task) {
|
private fun startEditTask(task: Task) {
|
||||||
editingTaskId = task.id
|
editingTaskId = task.id
|
||||||
taskListId = task.taskListId
|
taskListId = task.taskListId
|
||||||
title = task.name
|
title = task.name
|
||||||
@@ -84,8 +100,6 @@ class TaskViewModel @Inject constructor(
|
|||||||
} else {
|
} else {
|
||||||
createTaskUseCase(taskListId!!, title, description, priority, dueDate)
|
createTaskUseCase(taskListId!!, title, description, priority, dueDate)
|
||||||
}
|
}
|
||||||
// reset state after save
|
|
||||||
reset()
|
|
||||||
onDone?.invoke()
|
onDone?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user