2
2
3
3
package com.kip.reykunyu.ui.screens
4
4
5
+ import android.util.Log
6
+ import androidx.compose.animation.AnimatedContent
7
+ import androidx.compose.animation.ExperimentalAnimationApi
8
+ import androidx.compose.animation.core.animateFloatAsState
9
+ import androidx.compose.animation.core.tween
10
+ import androidx.compose.foundation.ExperimentalFoundationApi
5
11
import androidx.compose.foundation.layout.*
12
+ import androidx.compose.foundation.lazy.LazyColumn
13
+ import androidx.compose.foundation.lazy.LazyListState
14
+ import androidx.compose.foundation.lazy.items
15
+ import androidx.compose.foundation.lazy.rememberLazyListState
16
+ import androidx.compose.foundation.pager.HorizontalPager
17
+ import androidx.compose.foundation.pager.rememberPagerState
6
18
import androidx.compose.foundation.text.KeyboardActions
7
19
import androidx.compose.foundation.text.KeyboardOptions
8
20
import androidx.compose.material.icons.Icons
9
21
import androidx.compose.material.icons.filled.Menu
10
22
import androidx.compose.material.icons.filled.Search
23
+ import androidx.compose.material.icons.rounded.Search
11
24
import androidx.compose.material3.*
12
25
import androidx.compose.runtime.*
13
26
import androidx.compose.ui.Alignment
14
27
import androidx.compose.ui.Modifier
28
+ import androidx.compose.ui.composed
29
+ import androidx.compose.ui.draw.drawWithContent
30
+ import androidx.compose.ui.geometry.Offset
31
+ import androidx.compose.ui.geometry.Size
15
32
import androidx.compose.ui.platform.LocalFocusManager
16
33
import androidx.compose.ui.res.stringResource
17
34
import androidx.compose.ui.text.input.ImeAction
35
+ import androidx.compose.ui.text.style.TextAlign
36
+ import androidx.compose.ui.text.style.TextOverflow
18
37
import androidx.compose.ui.tooling.preview.Preview
38
+ import androidx.compose.ui.unit.Dp
19
39
import androidx.compose.ui.unit.dp
20
40
import androidx.compose.ui.unit.sp
21
41
import androidx.lifecycle.viewmodel.compose.viewModel
22
42
import com.kip.reykunyu.R
43
+ import com.kip.reykunyu.data.dict.Navi
44
+ import com.kip.reykunyu.ui.NaviCard
23
45
import com.kip.reykunyu.viewmodels.DictSearchState
24
46
import com.kip.reykunyu.viewmodels.DictionarySearchViewModel
25
47
import com.kip.reykunyu.viewmodels.OfflineDictState
26
48
import com.kip.reykunyu.viewmodels.OfflineDictionaryViewModel
49
+ import kotlinx.coroutines.launch
27
50
28
51
29
52
// The class for the main dictionary UI
30
- @OptIn(ExperimentalMaterial3Api ::class )
53
+ @OptIn(ExperimentalMaterial3Api ::class , ExperimentalAnimationApi :: class )
31
54
@Preview
32
55
@Composable
33
56
fun DictionaryScreen (
@@ -86,15 +109,26 @@ fun DictionaryScreen(
86
109
LoadingView ()
87
110
}
88
111
is OfflineDictState .Loaded -> {
89
-
90
- when (val searchState = searchViewModel.dictSearchState) {
91
- is DictSearchState .Success -> {
92
- StatusText (text = searchState.result.toNavi.size.toString())
112
+ AnimatedContent (targetState = searchViewModel.dictSearchState)
113
+ { state ->
114
+ when (state) {
115
+ is DictSearchState .Success -> {
116
+ SearchDisplay (
117
+ fromNavi = state.result.fromNavi,
118
+ toNavi = state.result.toNavi,
119
+ naviAction = {
120
+ Log .i(" REYKUNYU" , " NAVI REF: $it " )
121
+ searchViewModel.updateSearchInput(it)
122
+ onSearch()
123
+ }
124
+ )
125
+ }
126
+ DictSearchState .Error -> StatusText (text = " ERROR!" )
127
+ DictSearchState .Loading -> LoadingView ()
128
+ DictSearchState .Standby -> StandbyHelpView ()
93
129
}
94
- DictSearchState .Error -> StatusText (text = " ERROR!" )
95
- DictSearchState .Loading -> StatusText (text = " LOADING..." )
96
- DictSearchState .Standby -> {}
97
130
}
131
+
98
132
}
99
133
is OfflineDictState .Error -> {
100
134
StatusText (text = " ERROR!" )
@@ -138,6 +172,28 @@ fun LoadingView() {
138
172
}
139
173
}
140
174
175
+ @Composable
176
+ fun StandbyHelpView () {
177
+ Column (
178
+ Modifier .fillMaxSize(),
179
+ horizontalAlignment = Alignment .CenterHorizontally ,
180
+ verticalArrangement = Arrangement .Center
181
+ ) {
182
+ Icon (Icons .Rounded .Search , null ,
183
+ modifier = Modifier
184
+ .size(200 .dp)
185
+ .padding(0 .dp)
186
+ )
187
+ Text (
188
+ text = stringResource(R .string.string_help),
189
+ style = MaterialTheme .typography.titleMedium,
190
+ textAlign = TextAlign .Center ,
191
+ modifier = Modifier .padding(horizontal = 40 .dp, vertical = 5 .dp)
192
+ )
193
+ Spacer (Modifier .padding(60 .dp))
194
+ }
195
+ }
196
+
141
197
142
198
@Composable
143
199
fun StatusText (
@@ -173,13 +229,98 @@ fun DictionarySearchBar(
173
229
{
174
230
Text (
175
231
text = stringResource(R .string.search),
176
- style = MaterialTheme .typography.titleMedium
232
+ style = MaterialTheme .typography.titleMedium.copy(fontSize = 20 .sp),
177
233
)
178
234
},
179
- textStyle = MaterialTheme .typography.titleMedium,
235
+ textStyle = MaterialTheme .typography.titleMedium.copy(fontSize = 20 .sp) ,
180
236
singleLine = true ,
181
237
modifier = modifier
182
238
.fillMaxWidth()
183
239
.heightIn(32 .dp)
184
240
)
185
241
}
242
+
243
+ @OptIn(ExperimentalFoundationApi ::class )
244
+ @Composable
245
+ fun SearchDisplay (fromNavi : List <Navi >, toNavi : List <Navi >, naviAction : (String ) -> Unit ) {
246
+ val initPage = if (fromNavi.isEmpty() && toNavi.isNotEmpty()) { 1 } else { 0 }
247
+
248
+ val state = rememberPagerState(initialPage = initPage)
249
+ val titles = listOf (" Na\' vi to Lang (${fromNavi.size} )" , " Lang to Na\' vi (${toNavi.size} )" )
250
+
251
+ val coroutineScope = rememberCoroutineScope()
252
+
253
+ Column {
254
+ TabRow (selectedTabIndex = state.currentPage) {
255
+ titles.forEachIndexed { index, title ->
256
+ Tab (
257
+ selected = state.currentPage == index,
258
+ onClick = {
259
+ coroutineScope.launch {
260
+ state.animateScrollToPage(index)
261
+ }
262
+ },
263
+ text = { Text (text = title, maxLines = 2 , overflow = TextOverflow .Ellipsis ) }
264
+ )
265
+ }
266
+ }
267
+
268
+ HorizontalPager (pageCount = 2 , state = state) {
269
+ when (state.currentPage) {
270
+ 0 -> NaviList (naviList = fromNavi, naviAction = { naviAction(it) })
271
+ 1 -> NaviList (naviList = toNavi, naviAction = { naviAction(it) })
272
+ }
273
+ }
274
+ }
275
+ }
276
+
277
+ @Composable
278
+ fun NaviList (naviList : List <Navi >, naviAction : (String ) -> Unit ) {
279
+ val state: LazyListState = rememberLazyListState()
280
+ LazyColumn (
281
+ state = state,
282
+ modifier = Modifier .simpleVerticalScrollbar(state)
283
+ ) {
284
+ items(naviList) {item ->
285
+ NaviCard (navi = item, naviClick = { naviAction(it) })
286
+ }
287
+ }
288
+ }
289
+
290
+
291
+
292
+ fun Modifier.simpleVerticalScrollbar (
293
+ state : LazyListState ,
294
+ width : Dp = 8.dp
295
+ ): Modifier = composed {
296
+ val targetAlpha = if (state.isScrollInProgress) 1f else 0f
297
+ val duration = if (state.isScrollInProgress) 150 else 500
298
+
299
+ val alpha by animateFloatAsState(
300
+ targetValue = targetAlpha,
301
+ animationSpec = tween(durationMillis = duration)
302
+ )
303
+
304
+ val color = MaterialTheme .colorScheme.tertiary
305
+
306
+ drawWithContent {
307
+ drawContent()
308
+
309
+ val firstVisibleElementIndex = state.layoutInfo.visibleItemsInfo.firstOrNull()?.index
310
+ val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f
311
+
312
+ // Draw scrollbar if scrolling or if the animation is still running and lazy column has content
313
+ if (needDrawScrollbar && firstVisibleElementIndex != null ) {
314
+ val elementHeight = this .size.height / state.layoutInfo.totalItemsCount
315
+ val scrollbarOffsetY = firstVisibleElementIndex * elementHeight
316
+ val scrollbarHeight = state.layoutInfo.visibleItemsInfo.size * elementHeight
317
+
318
+ drawRect(
319
+ color = color,
320
+ topLeft = Offset (this .size.width - width.toPx(), scrollbarOffsetY),
321
+ size = Size (width.toPx(), scrollbarHeight),
322
+ alpha = alpha
323
+ )
324
+ }
325
+ }
326
+ }
0 commit comments