Skip to content

Commit 36903ef

Browse files
committed
update ShopDetailView
1 parent 1a9b2b2 commit 36903ef

File tree

7 files changed

+757
-27
lines changed

7 files changed

+757
-27
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.nativeapptemplate.nativeapptemplatefree.ui.shop_detail
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.material3.MaterialTheme
9+
import androidx.compose.material3.Text
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Alignment
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.platform.LocalDensity
14+
import androidx.compose.ui.unit.dp
15+
import com.nativeapptemplate.nativeapptemplatefree.model.Data
16+
import com.nativeapptemplate.nativeapptemplatefree.model.ItemTagState
17+
import com.nativeapptemplate.nativeapptemplatefree.model.ScanState
18+
import com.nativeapptemplate.nativeapptemplatefree.ui.common.tags.CompletedTag
19+
import com.nativeapptemplate.nativeapptemplatefree.ui.common.tags.CustomerScannedTag
20+
import com.nativeapptemplate.nativeapptemplatefree.ui.common.tags.IdlingTag
21+
import com.nativeapptemplate.nativeapptemplatefree.utils.DateUtility.cardTimeString
22+
23+
@Composable
24+
fun ShopDetailCardView(
25+
data: Data,
26+
) {
27+
Row(
28+
horizontalArrangement = Arrangement.Center,
29+
verticalAlignment = Alignment.CenterVertically,
30+
modifier = Modifier.padding(16.dp),
31+
) {
32+
val queueNumberFontSize = with(LocalDensity.current) { MaterialTheme.typography.titleLarge.fontSize.value.dp.toSp() }
33+
val timestampFontSize = with(LocalDensity.current) { MaterialTheme.typography.bodySmall.fontSize.value.dp.toSp() }
34+
val customerReadAt = data.getCustomerReadAt()
35+
val completedAt = data.getCompletedAt()
36+
37+
Text(
38+
data.getQueueNumber(),
39+
style = MaterialTheme.typography.titleLarge,
40+
fontSize = queueNumberFontSize,
41+
)
42+
43+
Spacer(modifier = Modifier.weight(1f))
44+
45+
Column(
46+
horizontalAlignment = Alignment.End
47+
) {
48+
49+
data.getScanState()?.let { scanState ->
50+
if (scanState == ScanState.Scanned) {
51+
CustomerScannedTag()
52+
Text(
53+
customerReadAt.cardTimeString(),
54+
color = MaterialTheme.colorScheme.onSurfaceVariant,
55+
modifier = Modifier
56+
.padding(top = 4.dp),
57+
fontSize = timestampFontSize,
58+
)
59+
}
60+
}
61+
}
62+
63+
Spacer(modifier = Modifier.weight(1f))
64+
65+
Column(
66+
horizontalAlignment = Alignment.End
67+
) {
68+
data.getItemTagState()?.let { itemTagState ->
69+
when (itemTagState) {
70+
ItemTagState.Completed -> {
71+
CompletedTag()
72+
73+
Text(
74+
completedAt.cardTimeString(),
75+
color = MaterialTheme.colorScheme.onSurfaceVariant,
76+
modifier = Modifier
77+
.padding(top = 4.dp),
78+
fontSize = timestampFontSize,
79+
)
80+
}
81+
else -> {
82+
IdlingTag()
83+
}
84+
}
85+
}
86+
}
87+
}
88+
}

app/src/main/kotlin/com/nativeapptemplate/nativeapptemplatefree/ui/shop_detail/ShopDetailView.kt

Lines changed: 210 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,56 @@ import androidx.compose.foundation.layout.fillMaxHeight
77
import androidx.compose.foundation.layout.fillMaxSize
88
import androidx.compose.foundation.layout.fillMaxWidth
99
import androidx.compose.foundation.layout.padding
10-
import androidx.compose.foundation.rememberScrollState
11-
import androidx.compose.foundation.verticalScroll
10+
import androidx.compose.foundation.layout.size
11+
import androidx.compose.foundation.layout.width
12+
import androidx.compose.foundation.lazy.LazyColumn
13+
import androidx.compose.foundation.lazy.itemsIndexed
1214
import androidx.compose.material.icons.Icons
1315
import androidx.compose.material.icons.automirrored.filled.ArrowBack
16+
import androidx.compose.material.icons.filled.Close
1417
import androidx.compose.material.icons.filled.Settings
18+
import androidx.compose.material.icons.outlined.Info
1519
import androidx.compose.material3.CenterAlignedTopAppBar
1620
import androidx.compose.material3.ExperimentalMaterial3Api
21+
import androidx.compose.material3.HorizontalDivider
1722
import androidx.compose.material3.Icon
1823
import androidx.compose.material3.IconButton
24+
import androidx.compose.material3.InputChip
25+
import androidx.compose.material3.InputChipDefaults
1926
import androidx.compose.material3.MaterialTheme
2027
import androidx.compose.material3.Scaffold
2128
import androidx.compose.material3.SnackbarDuration
29+
import androidx.compose.material3.Surface
2230
import androidx.compose.material3.Text
2331
import androidx.compose.material3.TopAppBarDefaults
32+
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator
33+
import androidx.compose.material3.pulltorefresh.pullToRefresh
34+
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
2435
import androidx.compose.runtime.Composable
2536
import androidx.compose.runtime.LaunchedEffect
2637
import androidx.compose.runtime.getValue
2738
import androidx.compose.ui.Alignment
2839
import androidx.compose.ui.Modifier
29-
import androidx.compose.ui.text.style.TextAlign
40+
import androidx.compose.ui.graphics.Color
41+
import androidx.compose.ui.res.stringResource
42+
import androidx.compose.ui.text.LinkAnnotation
43+
import androidx.compose.ui.text.SpanStyle
44+
import androidx.compose.ui.text.TextLinkStyles
45+
import androidx.compose.ui.text.buildAnnotatedString
46+
import androidx.compose.ui.text.withLink
47+
import androidx.compose.ui.text.withStyle
3048
import androidx.compose.ui.unit.dp
3149
import androidx.hilt.navigation.compose.hiltViewModel
3250
import androidx.lifecycle.Lifecycle
3351
import androidx.lifecycle.compose.LifecycleEventEffect
3452
import androidx.lifecycle.compose.collectAsStateWithLifecycle
53+
import com.nativeapptemplate.nativeapptemplatefree.NatConstants
54+
import com.nativeapptemplate.nativeapptemplatefree.R
55+
import com.nativeapptemplate.nativeapptemplatefree.model.ItemTagState.*
56+
import com.nativeapptemplate.nativeapptemplatefree.ui.common.ActionText
3557
import com.nativeapptemplate.nativeapptemplatefree.ui.common.ErrorView
3658
import com.nativeapptemplate.nativeapptemplatefree.ui.common.LoadingView
59+
import com.nativeapptemplate.nativeapptemplatefree.ui.common.SwipeableItemWithActions
3760

3861
@Composable
3962
internal fun ShopDetailView(
@@ -89,6 +112,7 @@ private fun ContentView(
89112
ShopDetailLoadingView(uiState, onSettingsClick, onBackClick)
90113
} else if (uiState.success) {
91114
ShopDetailContentView(
115+
viewModel = viewModel,
92116
uiState = uiState,
93117
onSettingsClick = onSettingsClick,
94118
onBackClick = onBackClick,
@@ -98,12 +122,16 @@ private fun ContentView(
98122
}
99123
}
100124

125+
@OptIn(ExperimentalMaterial3Api::class)
101126
@Composable
102127
private fun ShopDetailContentView(
128+
viewModel: ShopDetailViewModel,
103129
uiState: ShopDetailUiState,
104130
onSettingsClick: (String) -> Unit,
105131
onBackClick: () -> Unit,
106132
) {
133+
val itemTags = uiState.itemTags.getDatumWithRelationships().toMutableList()
134+
107135
Scaffold(
108136
topBar = {
109137
TopAppBar(
@@ -114,39 +142,200 @@ private fun ShopDetailContentView(
114142
},
115143
modifier = Modifier.fillMaxSize(),
116144
) { padding ->
145+
val pullToRefreshState = rememberPullToRefreshState()
146+
117147
Box(
118148
modifier = Modifier
119149
.fillMaxWidth()
120150
.fillMaxHeight()
151+
.pullToRefresh(
152+
state = pullToRefreshState,
153+
isRefreshing = uiState.isLoading,
154+
onRefresh = viewModel::reload,
155+
)
121156
.padding(padding)
122157
) {
123-
Column(
124-
horizontalAlignment = Alignment.CenterHorizontally,
125-
verticalArrangement = Arrangement.spacedBy(24.dp),
126-
modifier = Modifier
127-
.verticalScroll(rememberScrollState())
128-
.padding(16.dp)
158+
LazyColumn(
159+
Modifier.padding(24.dp)
129160
) {
130-
Text(
131-
uiState.shop.getName(),
132-
textAlign = TextAlign.Center,
133-
style = MaterialTheme.typography.displaySmall,
134-
modifier = Modifier
135-
.fillMaxWidth()
161+
item {
162+
if (!uiState.didShowReadInstructionsTip) {
163+
ReadInstructionsTip(
164+
stringResource(R.string.read_instructions),
165+
) {
166+
viewModel.updateDidShowReadInstructionsTip(true)
167+
}
168+
}
169+
}
170+
171+
item {
172+
Surface(Modifier.fillParentMaxWidth()) {
173+
Header(
174+
uiState = uiState
175+
)
176+
}
177+
}
178+
itemsIndexed(
179+
items = uiState.itemTags.getDatumWithRelationships(),
180+
) { index, itemTag ->
181+
val itemTagState = itemTag.getItemTagState()
182+
183+
SwipeableItemWithActions(
184+
isRevealed = itemTag.isOptionsRevealed,
185+
onExpanded = {
186+
itemTags[index] = itemTag.copy(isOptionsRevealed = true)
187+
},
188+
onCollapsed = {
189+
itemTags[index] = itemTag.copy(isOptionsRevealed = false)
190+
},
191+
actions = {
192+
if (itemTagState == Idled) {
193+
ActionText(
194+
onClick = {
195+
viewModel.completeItemTag(itemTag.id!!)
196+
},
197+
backgroundColor = Color.Blue,
198+
text = "Complete",
199+
modifier = Modifier
200+
.fillMaxHeight()
201+
.width(64.dp)
202+
)
203+
} else {
204+
ActionText(
205+
onClick = {
206+
viewModel.resetItemTag(itemTag.id!!)
207+
},
208+
backgroundColor = Color.Red,
209+
text = "Reset",
210+
modifier = Modifier.fillMaxHeight()
211+
)
212+
}
213+
},
214+
) {
215+
ShopDetailCardView(
216+
data = itemTag,
217+
)
218+
}
219+
220+
HorizontalDivider()
221+
}
222+
}
223+
Indicator(
224+
modifier = Modifier.align(Alignment.TopCenter),
225+
isRefreshing = uiState.isLoading,
226+
state = pullToRefreshState,
227+
)
228+
}
229+
}
230+
}
231+
232+
@Composable
233+
private fun Header(
234+
uiState: ShopDetailUiState,
235+
) {
236+
Column(
237+
horizontalAlignment = Alignment.Start,
238+
verticalArrangement = Arrangement.spacedBy(4.dp),
239+
) {
240+
Text(
241+
"${stringResource(R.string.instructions)}:",
242+
color = MaterialTheme.colorScheme.onSurfaceVariant,
243+
)
244+
245+
val instruction1 = buildAnnotatedString {
246+
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.onSurfaceVariant)) {
247+
append("1. ")
248+
append(stringResource(R.string.open))
249+
append(" ")
250+
}
251+
252+
withLink(
253+
LinkAnnotation.Url(
254+
uiState.shop.displayShopServerUrlString(NatConstants.baseUrlString()),
255+
TextLinkStyles(style = SpanStyle(color = MaterialTheme.colorScheme.primary))
136256
)
257+
) {
258+
append(stringResource(R.string.server_number_tags_webpage))
259+
}
260+
261+
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.onSurfaceVariant)) {
262+
append(".")
263+
}
264+
}
265+
266+
val instruction2 = buildAnnotatedString {
267+
append("2. ")
268+
append(stringResource(R.string.swipe_number_tag_below))
269+
append(" ")
270+
append(stringResource(R.string.tap_displayed_button))
271+
}
137272

138-
Text(
139-
uiState.shop.getDescription(),
140-
textAlign = TextAlign.Center,
141-
style = MaterialTheme.typography.displaySmall,
142-
modifier = Modifier
143-
.fillMaxWidth()
273+
val instruction3 = buildAnnotatedString {
274+
append("3. ")
275+
append(stringResource(R.string.server_number_tags_webpage_will_be_updated))
276+
}
277+
278+
val learnMore = buildAnnotatedString {
279+
withLink(
280+
LinkAnnotation.Url(
281+
NatConstants.HOW_TO_USE_URL,
282+
TextLinkStyles(style = SpanStyle(color = MaterialTheme.colorScheme.primary))
144283
)
284+
) {
285+
append(stringResource(R.string.learn_more))
145286
}
146287
}
288+
289+
Text(instruction1)
290+
291+
Text(
292+
instruction2,
293+
style = MaterialTheme.typography.bodyMedium,
294+
color = MaterialTheme.colorScheme.onSurfaceVariant,
295+
)
296+
Text(
297+
instruction3,
298+
style = MaterialTheme.typography.bodyMedium,
299+
color = MaterialTheme.colorScheme.onSurfaceVariant,
300+
)
301+
302+
Text(learnMore)
147303
}
148304
}
149305

306+
@Composable
307+
fun ReadInstructionsTip(
308+
text: String,
309+
onDismiss: () -> Unit,
310+
) {
311+
InputChip(
312+
onClick = {
313+
onDismiss()
314+
},
315+
label = { Text(
316+
text,
317+
style = MaterialTheme.typography.titleLarge,
318+
color = MaterialTheme.colorScheme.tertiary,
319+
) },
320+
selected = false,
321+
avatar = {
322+
Icon(
323+
Icons.Outlined.Info,
324+
contentDescription = null,
325+
tint = MaterialTheme.colorScheme.tertiary,
326+
modifier = Modifier.size(InputChipDefaults.AvatarSize)
327+
)
328+
},
329+
trailingIcon = {
330+
Icon(
331+
Icons.Default.Close,
332+
contentDescription = null,
333+
Modifier.size(InputChipDefaults.AvatarSize)
334+
)
335+
},
336+
)
337+
}
338+
150339
@OptIn(ExperimentalMaterial3Api::class)
151340
@Composable
152341
private fun TopAppBar(

0 commit comments

Comments
 (0)