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.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -49,6 +50,7 @@ fun AllTasksScreen(
|
||||
var showCompleteDialog by remember { mutableStateOf(false) }
|
||||
var showNewTaskDialog by remember { mutableStateOf(false) }
|
||||
var selectedTask by remember { mutableStateOf<TaskDetail?>(null) }
|
||||
var isRefreshing by remember { mutableStateOf(false) }
|
||||
|
||||
// Track which column to scroll to (from push notification navigation)
|
||||
var scrollToColumnIndex by remember { mutableStateOf<Int?>(null) }
|
||||
@@ -58,6 +60,13 @@ fun AllTasksScreen(
|
||||
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
|
||||
LaunchedEffect(navigateToTaskId, tasksState) {
|
||||
if (navigateToTaskId != null && tasksState is ApiResult.Success) {
|
||||
@@ -206,62 +215,71 @@ fun AllTasksScreen(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DynamicTaskKanbanView(
|
||||
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
|
||||
}
|
||||
PullToRefreshBox(
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = {
|
||||
isRefreshing = true
|
||||
viewModel.loadTasks(forceRefresh = true)
|
||||
},
|
||||
onEditTask = { task ->
|
||||
onNavigateToEditTask(task)
|
||||
},
|
||||
onCancelTask = { task ->
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
DynamicTaskKanbanView(
|
||||
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.loadTasks()
|
||||
// }
|
||||
},
|
||||
onUncancelTask = { task ->
|
||||
},
|
||||
onUncancelTask = { task ->
|
||||
// viewModel.uncancelTask(task.id) { _ ->
|
||||
// viewModel.loadTasks()
|
||||
// }
|
||||
},
|
||||
onMarkInProgress = { task ->
|
||||
viewModel.markInProgress(task.id) { success ->
|
||||
if (success) {
|
||||
viewModel.loadTasks()
|
||||
},
|
||||
onMarkInProgress = { task ->
|
||||
viewModel.markInProgress(task.id) { success ->
|
||||
if (success) {
|
||||
viewModel.loadTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onArchiveTask = { task ->
|
||||
viewModel.archiveTask(task.id) { success ->
|
||||
if (success) {
|
||||
viewModel.loadTasks()
|
||||
},
|
||||
onArchiveTask = { task ->
|
||||
viewModel.archiveTask(task.id) { success ->
|
||||
if (success) {
|
||||
viewModel.loadTasks()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onUnarchiveTask = { task ->
|
||||
viewModel.unarchiveTask(task.id) { success ->
|
||||
if (success) {
|
||||
viewModel.loadTasks()
|
||||
},
|
||||
onUnarchiveTask = { task ->
|
||||
viewModel.unarchiveTask(task.id) { success ->
|
||||
if (success) {
|
||||
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.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -32,6 +35,7 @@ fun HomeScreen(
|
||||
) {
|
||||
val summaryState by viewModel.myResidencesState.collectAsStateWithLifecycle()
|
||||
val totalSummary by DataManager.totalSummary.collectAsStateWithLifecycle()
|
||||
var isRefreshing by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.loadMyResidences()
|
||||
@@ -39,6 +43,13 @@ fun HomeScreen(
|
||||
taskViewModel.loadTasks()
|
||||
}
|
||||
|
||||
// Reset refresh state once the underlying load completes
|
||||
LaunchedEffect(summaryState) {
|
||||
if (summaryState !is ApiResult.Loading) {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
// Handle errors for loading summary
|
||||
summaryState.HandleErrors(
|
||||
onRetry = { viewModel.loadMyResidences() },
|
||||
@@ -65,10 +76,21 @@ fun HomeScreen(
|
||||
}
|
||||
) { paddingValues ->
|
||||
WarmGradientBackground {
|
||||
Column(
|
||||
PullToRefreshBox(
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = {
|
||||
isRefreshing = true
|
||||
viewModel.loadMyResidences(forceRefresh = true)
|
||||
taskViewModel.loadTasks(forceRefresh = true)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(horizontal = OrganicSpacing.comfortable, vertical = OrganicSpacing.cozy),
|
||||
verticalArrangement = Arrangement.spacedBy(OrganicSpacing.generous)
|
||||
) {
|
||||
@@ -199,6 +221,7 @@ fun HomeScreen(
|
||||
onClick = onNavigateToTasks
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -43,6 +44,7 @@ fun TasksScreen(
|
||||
var selectedTask by remember { mutableStateOf<com.tt.honeyDue.models.TaskDetail?>(null) }
|
||||
var showErrorDialog by remember { mutableStateOf(false) }
|
||||
var errorMessage by remember { mutableStateOf("") }
|
||||
var isRefreshing by remember { mutableStateOf(false) }
|
||||
|
||||
// Show error dialog when tasks fail to load
|
||||
LaunchedEffect(tasksState) {
|
||||
@@ -50,6 +52,9 @@ fun TasksScreen(
|
||||
errorMessage = com.tt.honeyDue.util.ErrorMessageParser.parse((tasksState as ApiResult.Error).message)
|
||||
showErrorDialog = true
|
||||
}
|
||||
if (tasksState !is ApiResult.Loading) {
|
||||
isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
@@ -138,12 +143,22 @@ fun TasksScreen(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PullToRefreshBox(
|
||||
isRefreshing = isRefreshing,
|
||||
onRefresh = {
|
||||
isRefreshing = true
|
||||
viewModel.loadTasks(forceRefresh = true)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
contentPadding = PaddingValues(
|
||||
top = paddingValues.calculateTopPadding() + OrganicSpacing.cozy,
|
||||
bottom = paddingValues.calculateBottomPadding() + OrganicSpacing.cozy,
|
||||
top = OrganicSpacing.cozy,
|
||||
bottom = OrganicSpacing.cozy,
|
||||
start = OrganicSpacing.cozy,
|
||||
end = OrganicSpacing.cozy
|
||||
),
|
||||
@@ -261,6 +276,7 @@ fun TasksScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user