Audit: PullToRefreshBox on remaining list screens (iOS parity)
HomeScreen + AllTasksScreen + TasksScreen now support pull-to-refresh. forceRefresh=true per CLAUDE.md mutation pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -49,6 +50,7 @@ fun AllTasksScreen(
|
|||||||
var showCompleteDialog by remember { mutableStateOf(false) }
|
var showCompleteDialog by remember { mutableStateOf(false) }
|
||||||
var showNewTaskDialog by remember { mutableStateOf(false) }
|
var showNewTaskDialog by remember { mutableStateOf(false) }
|
||||||
var selectedTask by remember { mutableStateOf<TaskDetail?>(null) }
|
var selectedTask by remember { mutableStateOf<TaskDetail?>(null) }
|
||||||
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Track which column to scroll to (from push notification navigation)
|
// Track which column to scroll to (from push notification navigation)
|
||||||
var scrollToColumnIndex by remember { mutableStateOf<Int?>(null) }
|
var scrollToColumnIndex by remember { mutableStateOf<Int?>(null) }
|
||||||
@@ -58,6 +60,13 @@ fun AllTasksScreen(
|
|||||||
residenceViewModel.loadMyResidences()
|
residenceViewModel.loadMyResidences()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset pull-to-refresh state once tasks finish loading
|
||||||
|
LaunchedEffect(tasksState) {
|
||||||
|
if (tasksState !is ApiResult.Loading) {
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// When tasks load and we have a pending navigation, find the column containing the task
|
// When tasks load and we have a pending navigation, find the column containing the task
|
||||||
LaunchedEffect(navigateToTaskId, tasksState) {
|
LaunchedEffect(navigateToTaskId, tasksState) {
|
||||||
if (navigateToTaskId != null && tasksState is ApiResult.Success) {
|
if (navigateToTaskId != null && tasksState is ApiResult.Success) {
|
||||||
@@ -206,62 +215,71 @@ fun AllTasksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DynamicTaskKanbanView(
|
PullToRefreshBox(
|
||||||
columns = taskData.columns,
|
isRefreshing = isRefreshing,
|
||||||
onCompleteTask = { task ->
|
onRefresh = {
|
||||||
if (onNavigateToCompleteTask != null) {
|
isRefreshing = true
|
||||||
// Use full-screen navigation
|
viewModel.loadTasks(forceRefresh = true)
|
||||||
val residenceName = (myResidencesState as? ApiResult.Success)
|
|
||||||
?.data?.residences?.find { it.id == task.residenceId }?.name ?: ""
|
|
||||||
onNavigateToCompleteTask(task, residenceName)
|
|
||||||
} else {
|
|
||||||
// Fall back to dialog
|
|
||||||
selectedTask = task
|
|
||||||
showCompleteDialog = true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onEditTask = { task ->
|
modifier = Modifier.fillMaxSize()
|
||||||
onNavigateToEditTask(task)
|
) {
|
||||||
},
|
DynamicTaskKanbanView(
|
||||||
onCancelTask = { task ->
|
columns = taskData.columns,
|
||||||
|
onCompleteTask = { task ->
|
||||||
|
if (onNavigateToCompleteTask != null) {
|
||||||
|
// Use full-screen navigation
|
||||||
|
val residenceName = (myResidencesState as? ApiResult.Success)
|
||||||
|
?.data?.residences?.find { it.id == task.residenceId }?.name ?: ""
|
||||||
|
onNavigateToCompleteTask(task, residenceName)
|
||||||
|
} else {
|
||||||
|
// Fall back to dialog
|
||||||
|
selectedTask = task
|
||||||
|
showCompleteDialog = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onEditTask = { task ->
|
||||||
|
onNavigateToEditTask(task)
|
||||||
|
},
|
||||||
|
onCancelTask = { task ->
|
||||||
// viewModel.cancelTask(task.id) { _ ->
|
// viewModel.cancelTask(task.id) { _ ->
|
||||||
// viewModel.loadTasks()
|
// viewModel.loadTasks()
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
onUncancelTask = { task ->
|
onUncancelTask = { task ->
|
||||||
// viewModel.uncancelTask(task.id) { _ ->
|
// viewModel.uncancelTask(task.id) { _ ->
|
||||||
// viewModel.loadTasks()
|
// viewModel.loadTasks()
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
onMarkInProgress = { task ->
|
onMarkInProgress = { task ->
|
||||||
viewModel.markInProgress(task.id) { success ->
|
viewModel.markInProgress(task.id) { success ->
|
||||||
if (success) {
|
if (success) {
|
||||||
viewModel.loadTasks()
|
viewModel.loadTasks()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
onArchiveTask = { task ->
|
||||||
onArchiveTask = { task ->
|
viewModel.archiveTask(task.id) { success ->
|
||||||
viewModel.archiveTask(task.id) { success ->
|
if (success) {
|
||||||
if (success) {
|
viewModel.loadTasks()
|
||||||
viewModel.loadTasks()
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
onUnarchiveTask = { task ->
|
||||||
onUnarchiveTask = { task ->
|
viewModel.unarchiveTask(task.id) { success ->
|
||||||
viewModel.unarchiveTask(task.id) { success ->
|
if (success) {
|
||||||
if (success) {
|
viewModel.loadTasks()
|
||||||
viewModel.loadTasks()
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier,
|
||||||
|
bottomPadding = bottomNavBarPadding,
|
||||||
|
scrollToColumnIndex = scrollToColumnIndex,
|
||||||
|
onScrollComplete = {
|
||||||
|
scrollToColumnIndex = null
|
||||||
|
onClearNavigateToTask()
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
modifier = Modifier,
|
}
|
||||||
bottomPadding = bottomNavBarPadding,
|
|
||||||
scrollToColumnIndex = scrollToColumnIndex,
|
|
||||||
onScrollComplete = {
|
|
||||||
scrollToColumnIndex = null
|
|
||||||
onClearNavigateToTask()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@ package com.tt.honeyDue.ui.screens
|
|||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -32,6 +35,7 @@ fun HomeScreen(
|
|||||||
) {
|
) {
|
||||||
val summaryState by viewModel.myResidencesState.collectAsStateWithLifecycle()
|
val summaryState by viewModel.myResidencesState.collectAsStateWithLifecycle()
|
||||||
val totalSummary by DataManager.totalSummary.collectAsStateWithLifecycle()
|
val totalSummary by DataManager.totalSummary.collectAsStateWithLifecycle()
|
||||||
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.loadMyResidences()
|
viewModel.loadMyResidences()
|
||||||
@@ -39,6 +43,13 @@ fun HomeScreen(
|
|||||||
taskViewModel.loadTasks()
|
taskViewModel.loadTasks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset refresh state once the underlying load completes
|
||||||
|
LaunchedEffect(summaryState) {
|
||||||
|
if (summaryState !is ApiResult.Loading) {
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle errors for loading summary
|
// Handle errors for loading summary
|
||||||
summaryState.HandleErrors(
|
summaryState.HandleErrors(
|
||||||
onRetry = { viewModel.loadMyResidences() },
|
onRetry = { viewModel.loadMyResidences() },
|
||||||
@@ -65,10 +76,21 @@ fun HomeScreen(
|
|||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
WarmGradientBackground {
|
WarmGradientBackground {
|
||||||
Column(
|
PullToRefreshBox(
|
||||||
|
isRefreshing = isRefreshing,
|
||||||
|
onRefresh = {
|
||||||
|
isRefreshing = true
|
||||||
|
viewModel.loadMyResidences(forceRefresh = true)
|
||||||
|
taskViewModel.loadTasks(forceRefresh = true)
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
.padding(horizontal = OrganicSpacing.comfortable, vertical = OrganicSpacing.cozy),
|
.padding(horizontal = OrganicSpacing.comfortable, vertical = OrganicSpacing.cozy),
|
||||||
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.generous)
|
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.generous)
|
||||||
) {
|
) {
|
||||||
@@ -199,6 +221,7 @@ fun HomeScreen(
|
|||||||
onClick = onNavigateToTasks
|
onClick = onNavigateToTasks
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -43,6 +44,7 @@ fun TasksScreen(
|
|||||||
var selectedTask by remember { mutableStateOf<com.tt.honeyDue.models.TaskDetail?>(null) }
|
var selectedTask by remember { mutableStateOf<com.tt.honeyDue.models.TaskDetail?>(null) }
|
||||||
var showErrorDialog by remember { mutableStateOf(false) }
|
var showErrorDialog by remember { mutableStateOf(false) }
|
||||||
var errorMessage by remember { mutableStateOf("") }
|
var errorMessage by remember { mutableStateOf("") }
|
||||||
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
// Show error dialog when tasks fail to load
|
// Show error dialog when tasks fail to load
|
||||||
LaunchedEffect(tasksState) {
|
LaunchedEffect(tasksState) {
|
||||||
@@ -50,6 +52,9 @@ fun TasksScreen(
|
|||||||
errorMessage = com.tt.honeyDue.util.ErrorMessageParser.parse((tasksState as ApiResult.Error).message)
|
errorMessage = com.tt.honeyDue.util.ErrorMessageParser.parse((tasksState as ApiResult.Error).message)
|
||||||
showErrorDialog = true
|
showErrorDialog = true
|
||||||
}
|
}
|
||||||
|
if (tasksState !is ApiResult.Loading) {
|
||||||
|
isRefreshing = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -138,12 +143,22 @@ fun TasksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
PullToRefreshBox(
|
||||||
|
isRefreshing = isRefreshing,
|
||||||
|
onRefresh = {
|
||||||
|
isRefreshing = true
|
||||||
|
viewModel.loadTasks(forceRefresh = true)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(
|
||||||
top = paddingValues.calculateTopPadding() + OrganicSpacing.cozy,
|
top = OrganicSpacing.cozy,
|
||||||
bottom = paddingValues.calculateBottomPadding() + OrganicSpacing.cozy,
|
bottom = OrganicSpacing.cozy,
|
||||||
start = OrganicSpacing.cozy,
|
start = OrganicSpacing.cozy,
|
||||||
end = OrganicSpacing.cozy
|
end = OrganicSpacing.cozy
|
||||||
),
|
),
|
||||||
@@ -261,6 +276,7 @@ fun TasksScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user