1
1
package com.wei.picquest.feature.photo.photosearch
2
2
3
+ import androidx.compose.foundation.clickable
4
+ import androidx.compose.foundation.layout.Arrangement
3
5
import androidx.compose.foundation.layout.Column
6
+ import androidx.compose.foundation.layout.Row
4
7
import androidx.compose.foundation.layout.Spacer
5
8
import androidx.compose.foundation.layout.WindowInsets
6
9
import androidx.compose.foundation.layout.fillMaxSize
10
+ import androidx.compose.foundation.layout.fillMaxWidth
11
+ import androidx.compose.foundation.layout.padding
7
12
import androidx.compose.foundation.layout.safeDrawing
8
13
import androidx.compose.foundation.layout.windowInsetsBottomHeight
9
14
import androidx.compose.foundation.layout.windowInsetsTopHeight
10
- import androidx.compose.material3.Button
15
+ import androidx.compose.foundation.lazy.LazyColumn
16
+ import androidx.compose.foundation.lazy.items
17
+ import androidx.compose.foundation.shape.RoundedCornerShape
18
+ import androidx.compose.foundation.text.KeyboardActions
19
+ import androidx.compose.foundation.text.KeyboardOptions
20
+ import androidx.compose.material3.Icon
21
+ import androidx.compose.material3.IconButton
11
22
import androidx.compose.material3.MaterialTheme
12
23
import androidx.compose.material3.Surface
13
24
import androidx.compose.material3.Text
25
+ import androidx.compose.material3.TextField
26
+ import androidx.compose.material3.TextFieldDefaults
14
27
import androidx.compose.runtime.Composable
28
+ import androidx.compose.runtime.LaunchedEffect
29
+ import androidx.compose.runtime.getValue
15
30
import androidx.compose.runtime.mutableStateOf
16
31
import androidx.compose.runtime.remember
17
32
import androidx.compose.ui.Alignment
33
+ import androidx.compose.ui.ExperimentalComposeUiApi
18
34
import androidx.compose.ui.Modifier
19
- import androidx.compose.ui.semantics.contentDescription
20
- import androidx.compose.ui.semantics.semantics
35
+ import androidx.compose.ui.focus.FocusRequester
36
+ import androidx.compose.ui.focus.focusRequester
37
+ import androidx.compose.ui.graphics.Color
38
+ import androidx.compose.ui.input.key.Key
39
+ import androidx.compose.ui.input.key.key
40
+ import androidx.compose.ui.input.key.onKeyEvent
41
+ import androidx.compose.ui.platform.LocalSoftwareKeyboardController
42
+ import androidx.compose.ui.platform.testTag
43
+ import androidx.compose.ui.res.stringResource
44
+ import androidx.compose.ui.text.SpanStyle
45
+ import androidx.compose.ui.text.buildAnnotatedString
46
+ import androidx.compose.ui.text.font.FontWeight
47
+ import androidx.compose.ui.text.input.ImeAction
48
+ import androidx.compose.ui.text.withStyle
49
+ import androidx.compose.ui.unit.dp
50
+ import androidx.hilt.navigation.compose.hiltViewModel
51
+ import androidx.lifecycle.compose.collectAsStateWithLifecycle
21
52
import androidx.navigation.NavController
22
53
import com.wei.picquest.core.designsystem.component.FunctionalityNotAvailablePopup
23
54
import com.wei.picquest.core.designsystem.component.ThemePreviews
55
+ import com.wei.picquest.core.designsystem.icon.PqIcons
24
56
import com.wei.picquest.core.designsystem.theme.PqTheme
57
+ import com.wei.picquest.core.designsystem.theme.SPACING_LARGE
58
+ import com.wei.picquest.core.designsystem.theme.SPACING_SMALL
59
+ import com.wei.picquest.feature.photo.R
60
+ import com.wei.picquest.feature.photo.photolibrary.navigation.navigateToPhotoLibrary
25
61
26
62
/* *
27
63
*
@@ -54,15 +90,38 @@ import com.wei.picquest.core.designsystem.theme.PqTheme
54
90
*/
55
91
@Composable
56
92
internal fun PhotoSearchRoute (
57
- onSearchClick : (String ) -> Unit ,
58
93
navController : NavController ,
94
+ viewModel : PhotoSearchViewModel = hiltViewModel(),
59
95
) {
60
- PhotoSearchScreen (onSearchClick = onSearchClick)
96
+ val uiStates: PhotoSearchViewState by viewModel.states.collectAsStateWithLifecycle()
97
+
98
+ PhotoSearchScreen (
99
+ uiStates = uiStates,
100
+ onSearchQueryChanged = {
101
+ viewModel.dispatch(PhotoSearchViewAction .SearchQueryChanged (it))
102
+ },
103
+
104
+ onSearchTriggered = {
105
+ viewModel.dispatch(PhotoSearchViewAction .SearchTriggered (it))
106
+ navController.navigateToPhotoLibrary(it)
107
+ },
108
+ onRecentSearchClicked = {
109
+ viewModel.dispatch(PhotoSearchViewAction .RecentSearchClicked (it))
110
+ navController.navigateToPhotoLibrary(it)
111
+ },
112
+ onClearRecentSearches = {
113
+ viewModel.dispatch(PhotoSearchViewAction .ClearRecentSearchQueriesClicked )
114
+ },
115
+ )
61
116
}
62
117
63
118
@Composable
64
119
internal fun PhotoSearchScreen (
65
- onSearchClick : (String ) -> Unit ,
120
+ uiStates : PhotoSearchViewState ,
121
+ onSearchQueryChanged : (String ) -> Unit ,
122
+ onSearchTriggered : (String ) -> Unit ,
123
+ onRecentSearchClicked : (String ) -> Unit ,
124
+ onClearRecentSearches : () -> Unit ,
66
125
withTopSpacer : Boolean = true,
67
126
withBottomSpacer : Boolean = true,
68
127
) {
@@ -88,19 +147,17 @@ internal fun PhotoSearchScreen(
88
147
}
89
148
90
149
Column {
91
- Spacer (modifier = Modifier .weight(1f ))
92
- Text (
93
- text = " Search Screen not available \uD83D\uDE48 " ,
94
- color = MaterialTheme .colorScheme.error,
95
- style = MaterialTheme .typography.headlineMedium,
96
- modifier = Modifier
97
- .semantics { contentDescription = " " },
150
+ SearchTextField (
151
+ onSearchQueryChanged = onSearchQueryChanged,
152
+ onSearchTriggered = onSearchTriggered,
153
+ searchQuery = uiStates.searchQuery,
154
+ )
155
+ RecentSearchesBody (
156
+ modifier = Modifier .weight(1f ),
157
+ onClearRecentSearches = onClearRecentSearches,
158
+ onRecentSearchClicked = onRecentSearchClicked,
159
+ recentSearchQueries = uiStates.recentSearchQueries,
98
160
)
99
- // TODO Wei
100
- Button (onClick = { onSearchClick(" kitten" ) }) {
101
- Text (text = " Search [kitten] keyword" )
102
- }
103
- Spacer (modifier = Modifier .weight(1f ))
104
161
}
105
162
106
163
if (withBottomSpacer) {
@@ -110,10 +167,157 @@ internal fun PhotoSearchScreen(
110
167
}
111
168
}
112
169
170
+ @Composable
171
+ private fun RecentSearchesBody (
172
+ modifier : Modifier = Modifier ,
173
+ onClearRecentSearches : () -> Unit ,
174
+ onRecentSearchClicked : (String ) -> Unit ,
175
+ recentSearchQueries : List <String >,
176
+ ) {
177
+ Column (modifier = modifier) {
178
+ Row (
179
+ horizontalArrangement = Arrangement .SpaceBetween ,
180
+ verticalAlignment = Alignment .CenterVertically ,
181
+ modifier = Modifier .fillMaxWidth(),
182
+ ) {
183
+ Text (
184
+ text = buildAnnotatedString {
185
+ withStyle(style = SpanStyle (fontWeight = FontWeight .Bold )) {
186
+ append(stringResource(id = R .string.recent_searches))
187
+ }
188
+ },
189
+ modifier = Modifier .padding(
190
+ horizontal = SPACING_LARGE .dp,
191
+ vertical = SPACING_SMALL .dp,
192
+ ),
193
+ )
194
+ if (recentSearchQueries.isNotEmpty()) {
195
+ IconButton (
196
+ onClick = {
197
+ onClearRecentSearches()
198
+ },
199
+ modifier = Modifier .padding(horizontal = SPACING_LARGE .dp),
200
+ ) {
201
+ Icon (
202
+ imageVector = PqIcons .Close ,
203
+ contentDescription = stringResource(
204
+ id = R .string.clear_recent_searches_content_desc,
205
+ ),
206
+ tint = MaterialTheme .colorScheme.onSurface,
207
+ )
208
+ }
209
+ }
210
+ }
211
+ LazyColumn (modifier = Modifier .padding(horizontal = SPACING_LARGE .dp)) {
212
+ items(recentSearchQueries) { recentSearch ->
213
+ Text (
214
+ text = recentSearch,
215
+ style = MaterialTheme .typography.headlineSmall,
216
+ modifier = Modifier
217
+ .padding(vertical = SPACING_LARGE .dp)
218
+ .clickable { onRecentSearchClicked(recentSearch) }
219
+ .fillMaxWidth(),
220
+ )
221
+ }
222
+ }
223
+ }
224
+ }
225
+
226
+ @OptIn(ExperimentalComposeUiApi ::class )
227
+ @Composable
228
+ private fun SearchTextField (
229
+ onSearchQueryChanged : (String ) -> Unit ,
230
+ searchQuery : String ,
231
+ onSearchTriggered : (String ) -> Unit ,
232
+ ) {
233
+ val focusRequester = remember { FocusRequester () }
234
+ val keyboardController = LocalSoftwareKeyboardController .current
235
+
236
+ val onSearchExplicitlyTriggered = {
237
+ keyboardController?.hide()
238
+ onSearchTriggered(searchQuery)
239
+ }
240
+
241
+ TextField (
242
+ colors = TextFieldDefaults .colors(
243
+ focusedIndicatorColor = Color .Transparent ,
244
+ unfocusedIndicatorColor = Color .Transparent ,
245
+ disabledIndicatorColor = Color .Transparent ,
246
+ ),
247
+ leadingIcon = {
248
+ Icon (
249
+ imageVector = PqIcons .Search ,
250
+ contentDescription = stringResource(R .string.search),
251
+ tint = MaterialTheme .colorScheme.onSurface,
252
+ )
253
+ },
254
+ trailingIcon = {
255
+ if (searchQuery.isNotEmpty()) {
256
+ IconButton (
257
+ onClick = {
258
+ onSearchQueryChanged(" " )
259
+ },
260
+ ) {
261
+ Icon (
262
+ imageVector = PqIcons .Close ,
263
+ contentDescription = stringResource(R .string.clear_search_text_content_desc),
264
+ tint = MaterialTheme .colorScheme.onSurface,
265
+ )
266
+ }
267
+ }
268
+ },
269
+ onValueChange = {
270
+ if (! it.contains(" \n " )) {
271
+ onSearchQueryChanged(it)
272
+ }
273
+ },
274
+ modifier = Modifier
275
+ .fillMaxWidth()
276
+ .padding(SPACING_LARGE .dp)
277
+ .focusRequester(focusRequester)
278
+ .onKeyEvent {
279
+ if (it.key == Key .Enter ) {
280
+ onSearchExplicitlyTriggered()
281
+ true
282
+ } else {
283
+ false
284
+ }
285
+ }
286
+ .testTag(" searchTextField" ),
287
+ shape = RoundedCornerShape (32 .dp),
288
+ value = searchQuery,
289
+ keyboardOptions = KeyboardOptions (
290
+ imeAction = ImeAction .Search ,
291
+ ),
292
+ keyboardActions = KeyboardActions (
293
+ onSearch = {
294
+ onSearchExplicitlyTriggered()
295
+ },
296
+ ),
297
+ maxLines = 1 ,
298
+ singleLine = true ,
299
+ )
300
+ LaunchedEffect (Unit ) {
301
+ focusRequester.requestFocus()
302
+ }
303
+ }
304
+
113
305
@ThemePreviews
114
306
@Composable
115
307
fun SearchPhotoScreenPreview () {
116
308
PqTheme {
117
- PhotoSearchScreen (onSearchClick = {})
309
+ PhotoSearchScreen (
310
+ uiStates = PhotoSearchViewState (
311
+ searchQuery = " cat" ,
312
+ recentSearchQueries = listOf (
313
+ " cat" ,
314
+ " mouse" ,
315
+ ),
316
+ ),
317
+ onSearchQueryChanged = {},
318
+ onSearchTriggered = {},
319
+ onRecentSearchClicked = {},
320
+ onClearRecentSearches = {},
321
+ )
118
322
}
119
323
}
0 commit comments