@@ -31,6 +31,8 @@ import androidx.compose.material3.Scaffold
31import androidx.compose.material3.Text
31import androidx.compose.material3.Text
32import androidx.compose.material3.TextButton
32import androidx.compose.material3.TextButton
33import androidx.compose.material3.TopAppBar
33import androidx.compose.material3.TopAppBar
34+import androidx.compose.material3.pulltorefresh.PullToRefreshBox
35+import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
34import androidx.compose.runtime.Composable
36import androidx.compose.runtime.Composable
35import androidx.compose.runtime.getValue
37import androidx.compose.runtime.getValue
36import androidx.compose.runtime.mutableStateOf
38import androidx.compose.runtime.mutableStateOf
@@ -38,6 +40,8 @@ import androidx.compose.runtime.remember
38import androidx.compose.runtime.setValue
40import androidx.compose.runtime.setValue
39import androidx.compose.ui.Alignment
41import androidx.compose.ui.Alignment
40import androidx.compose.ui.Modifier
42import androidx.compose.ui.Modifier
43+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
44+import androidx.compose.ui.platform.LocalHapticFeedback
41import androidx.compose.ui.text.style.TextOverflow
45import androidx.compose.ui.text.style.TextOverflow
42import androidx.compose.ui.unit.dp
46import androidx.compose.ui.unit.dp
43import soy.iko.opencode.core.Event
47import soy.iko.opencode.core.Event
@@ -47,6 +51,7 @@ import soy.iko.opencode.core.UiState
47@OptIn(ExperimentalMaterial3Api::class)
51@OptIn(ExperimentalMaterial3Api::class)
48@Composable
52@Composable
49fun SessionsScreen(state: UiState, dispatch: (Event) -> Unit) {
53fun SessionsScreen(state: UiState, dispatch: (Event) -> Unit) {
54+ val haptic = LocalHapticFeedback.current
50 var pendingDeleteId by remember { mutableStateOf<String?>(null) }
55 var pendingDeleteId by remember { mutableStateOf<String?>(null) }
52 pendingDeleteId?.let { deleteId ->
57 pendingDeleteId?.let { deleteId ->
@@ -57,6 +62,7 @@ fun SessionsScreen(state: UiState, dispatch: (Event) -> Unit) {
57 text = { Text(pendingSession?.title ?: deleteId) },
62 text = { Text(pendingSession?.title ?: deleteId) },
58 confirmButton = {
63 confirmButton = {
59 TextButton(onClick = {
64 TextButton(onClick = {
65+ haptic.performHapticFeedback(HapticFeedbackType.LongPress)
60 dispatch(Event.DeleteSession(deleteId))
66 dispatch(Event.DeleteSession(deleteId))
61 pendingDeleteId = null
67 pendingDeleteId = null
@@ -101,17 +107,24 @@ fun SessionsScreen(state: UiState, dispatch: (Event) -> Unit) {
103 state.sessions.isEmpty() && !state.loading -> EmptySessions()
109 state.sessions.isEmpty() && !state.loading -> EmptySessions()
104− else -> LazyColumn(
110+ else -> PullToRefreshBox(
111+ isRefreshing = state.loading,
112+ onRefresh = { dispatch(Event.LoadSessions) },
105 modifier = Modifier.fillMaxSize(),
113 modifier = Modifier.fillMaxSize(),
106− contentPadding = PaddingValues(bottom = 88.dp),
114+ state = rememberPullToRefreshState(),
117+ modifier = Modifier.fillMaxSize(),
118+ contentPadding = PaddingValues(bottom = 88.dp),
108− items(state.sessions, key = { it.id }) { session ->
120+ items(state.sessions, key = { it.id }) { session ->
109− SessionRow(
121+ SessionRow(
110− session = session,
122+ session = session,
111− onOpen = { dispatch(Event.SelectSession(session.id)) },
123+ onOpen = { dispatch(Event.SelectSession(session.id)) },
112− onDelete = { pendingDeleteId = session.id },
124+ onDelete = { pendingDeleteId = session.id },
114− HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.25f))
126+ HorizontalDivider(color = MaterialTheme.colorScheme.outline.copy(alpha = 0.25f))