1
1
package io.jja08111.gemini.feature.rooms.ui
2
2
3
3
import android.text.format.DateUtils
4
+ import androidx.compose.foundation.background
4
5
import androidx.compose.foundation.clickable
5
6
import androidx.compose.foundation.layout.Box
6
7
import androidx.compose.foundation.layout.Column
8
+ import androidx.compose.foundation.layout.Spacer
7
9
import androidx.compose.foundation.layout.fillMaxSize
8
10
import androidx.compose.foundation.layout.fillMaxWidth
11
+ import androidx.compose.foundation.layout.height
9
12
import androidx.compose.foundation.layout.padding
13
+ import androidx.compose.foundation.layout.size
10
14
import androidx.compose.foundation.lazy.LazyColumn
11
15
import androidx.compose.material.icons.Icons
12
16
import androidx.compose.material.icons.filled.Add
13
- import androidx.compose.material3.CircularProgressIndicator
14
17
import androidx.compose.material3.FloatingActionButton
15
18
import androidx.compose.material3.Icon
16
19
import androidx.compose.material3.LargeTopAppBar
@@ -22,14 +25,17 @@ import androidx.compose.material3.Text
22
25
import androidx.compose.material3.TopAppBarDefaults
23
26
import androidx.compose.material3.TopAppBarScrollBehavior
24
27
import androidx.compose.runtime.Composable
28
+ import androidx.compose.runtime.Stable
25
29
import androidx.compose.ui.Alignment
26
30
import androidx.compose.ui.Modifier
31
+ import androidx.compose.ui.draw.clip
27
32
import androidx.compose.ui.input.nestedscroll.nestedScroll
28
33
import androidx.compose.ui.input.pointer.PointerEventPass
29
34
import androidx.compose.ui.input.pointer.pointerInput
30
35
import androidx.compose.ui.input.pointer.positionChange
31
36
import androidx.compose.ui.res.stringResource
32
37
import androidx.compose.ui.text.style.TextOverflow
38
+ import androidx.compose.ui.tooling.preview.Preview
33
39
import androidx.compose.ui.unit.dp
34
40
import androidx.paging.LoadState
35
41
import androidx.paging.compose.collectAsLazyPagingItems
@@ -61,17 +67,9 @@ fun RoomsScreen(
61
67
val rooms = uiState.roomStream.collectAsLazyPagingItems()
62
68
val loadState = rooms.loadState
63
69
LazyColumn (modifier = Modifier .fillMaxSize()) {
64
- items(count = rooms.itemCount) { index ->
65
- val room = checkNotNull(rooms[index])
66
- RoomTile (
67
- modifier = Modifier .fillMaxWidth(),
68
- room = room,
69
- onClick = { onRoomClick(room.id) },
70
- )
71
- }
72
70
when (val refreshState = loadState.refresh) {
73
71
is LoadState .Loading -> {
74
- item { CircularProgressIndicator () }
72
+ items(count = 5 ) { RoomTileSkeleton () }
75
73
}
76
74
77
75
is LoadState .Error -> {
@@ -85,11 +83,20 @@ fun RoomsScreen(
85
83
}
86
84
}
87
85
88
- else -> {}
86
+ is LoadState .NotLoading -> {
87
+ items(count = rooms.itemCount) { index ->
88
+ val room = checkNotNull(rooms[index])
89
+ RoomTile (
90
+ modifier = Modifier .fillMaxWidth(),
91
+ room = room,
92
+ onClick = { onRoomClick(room.id) },
93
+ )
94
+ }
95
+ }
89
96
}
90
97
when (val appendState = loadState.append) {
91
98
is LoadState .Loading -> {
92
- item { CircularProgressIndicator () }
99
+ item { RoomTileSkeleton () }
93
100
}
94
101
95
102
is LoadState .Error -> {
@@ -103,7 +110,7 @@ fun RoomsScreen(
103
110
}
104
111
}
105
112
106
- else -> {}
113
+ is LoadState . NotLoading -> {}
107
114
}
108
115
}
109
116
FloatingActionButton (
@@ -145,13 +152,18 @@ fun Modifier.verticalScrollDisabled() =
145
152
}
146
153
}
147
154
155
+ @Stable
156
+ private fun Modifier.roomTilePadding (): Modifier {
157
+ return this .padding(horizontal = 16 .dp, vertical = 12 .dp)
158
+ }
159
+
148
160
// TODO: Show summarized user message.
149
161
@Composable
150
162
internal fun RoomTile (modifier : Modifier = Modifier , room : Room , onClick : () -> Unit ) {
151
163
Column (
152
164
modifier = modifier
153
165
.clickable(onClick = onClick)
154
- .padding(horizontal = 16 .dp, vertical = 12 .dp ),
166
+ .roomTilePadding( ),
155
167
) {
156
168
Text (
157
169
text = room.title ? : " New chat" ,
@@ -170,7 +182,31 @@ internal fun RoomTile(modifier: Modifier = Modifier, room: Room, onClick: () ->
170
182
}
171
183
}
172
184
173
- fun LocalDateTime.toTimeSpanText (): String {
185
+ @Composable
186
+ private fun Modifier.skeletonStyle (): Modifier {
187
+ return this
188
+ .clip(shape = MaterialTheme .shapes.extraSmall)
189
+ .background(color = MaterialTheme .colorScheme.onBackground.copy(alpha = 0.1f ))
190
+ }
191
+
192
+ @Composable
193
+ private fun RoomTileSkeleton () {
194
+ Column (modifier = Modifier .roomTilePadding()) {
195
+ Box (
196
+ modifier = Modifier
197
+ .size(width = 184 .dp, height = 20 .dp)
198
+ .skeletonStyle(),
199
+ )
200
+ Spacer (modifier = Modifier .height(8 .dp))
201
+ Box (
202
+ modifier = Modifier
203
+ .size(width = 60 .dp, height = 16 .dp)
204
+ .skeletonStyle(),
205
+ )
206
+ }
207
+ }
208
+
209
+ private fun LocalDateTime.toTimeSpanText (): String {
174
210
val now = Instant .now().toEpochMilli()
175
211
val defaultZone = ZoneId .systemDefault()
176
212
return DateUtils .getRelativeTimeSpanString(
@@ -179,3 +215,11 @@ fun LocalDateTime.toTimeSpanText(): String {
179
215
DateUtils .DAY_IN_MILLIS ,
180
216
).toString()
181
217
}
218
+
219
+ @Composable
220
+ @Preview(showBackground = true )
221
+ private fun RoomTileSkeletonPreview () {
222
+ MaterialTheme {
223
+ RoomTileSkeleton ()
224
+ }
225
+ }
0 commit comments