@@ -7,33 +7,56 @@ import androidx.compose.foundation.layout.fillMaxHeight
77import androidx.compose.foundation.layout.fillMaxSize
88import androidx.compose.foundation.layout.fillMaxWidth
99import 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
1214import androidx.compose.material.icons.Icons
1315import androidx.compose.material.icons.automirrored.filled.ArrowBack
16+ import androidx.compose.material.icons.filled.Close
1417import androidx.compose.material.icons.filled.Settings
18+ import androidx.compose.material.icons.outlined.Info
1519import androidx.compose.material3.CenterAlignedTopAppBar
1620import androidx.compose.material3.ExperimentalMaterial3Api
21+ import androidx.compose.material3.HorizontalDivider
1722import androidx.compose.material3.Icon
1823import androidx.compose.material3.IconButton
24+ import androidx.compose.material3.InputChip
25+ import androidx.compose.material3.InputChipDefaults
1926import androidx.compose.material3.MaterialTheme
2027import androidx.compose.material3.Scaffold
2128import androidx.compose.material3.SnackbarDuration
29+ import androidx.compose.material3.Surface
2230import androidx.compose.material3.Text
2331import 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
2435import androidx.compose.runtime.Composable
2536import androidx.compose.runtime.LaunchedEffect
2637import androidx.compose.runtime.getValue
2738import androidx.compose.ui.Alignment
2839import 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
3048import androidx.compose.ui.unit.dp
3149import androidx.hilt.navigation.compose.hiltViewModel
3250import androidx.lifecycle.Lifecycle
3351import androidx.lifecycle.compose.LifecycleEventEffect
3452import 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
3557import com.nativeapptemplate.nativeapptemplatefree.ui.common.ErrorView
3658import com.nativeapptemplate.nativeapptemplatefree.ui.common.LoadingView
59+ import com.nativeapptemplate.nativeapptemplatefree.ui.common.SwipeableItemWithActions
3760
3861@Composable
3962internal 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
102127private 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
152341private fun TopAppBar (
0 commit comments