@@ -13,6 +13,10 @@ import androidx.compose.runtime.*
13
13
import androidx.compose.ui.Alignment
14
14
import androidx.compose.ui.Modifier
15
15
import androidx.compose.ui.draw.clip
16
+ import androidx.compose.ui.focus.FocusRequester
17
+ import androidx.compose.ui.focus.focusRequester
18
+ import androidx.compose.ui.focus.focusTarget
19
+ import androidx.compose.ui.focus.onFocusChanged
16
20
import androidx.compose.ui.graphics.Color
17
21
import androidx.compose.ui.unit.dp
18
22
import androidx.compose.ui.unit.sp
@@ -23,11 +27,16 @@ import org.succlz123.app.acfun.base.AcBackButton
23
27
import org.succlz123.app.acfun.base.LoadingFailView
24
28
import org.succlz123.app.acfun.base.LoadingView
25
29
import org.succlz123.app.acfun.theme.ColorResource
30
+ import org.succlz123.app.acfun.ui.main.GlobalFocusViewModel
26
31
import org.succlz123.lib.click.noRippleClickable
27
32
import org.succlz123.lib.common.getPlatformName
28
33
import org.succlz123.lib.filedownloader.core.DownloadRequest
29
34
import org.succlz123.lib.filedownloader.core.DownloadStateType
30
35
import org.succlz123.lib.filedownloader.core.FileDownLoader
36
+ import org.succlz123.lib.focus.FocusNode
37
+ import org.succlz123.lib.focus.notAllowMove
38
+ import org.succlz123.lib.focus.onFocusKeyEventMove
39
+ import org.succlz123.lib.focus.onFocusParent
31
40
import org.succlz123.lib.image.AsyncImageUrlMultiPlatform
32
41
import org.succlz123.lib.screen.LocalScreenNavigator
33
42
import org.succlz123.lib.screen.LocalScreenRecord
@@ -54,6 +63,7 @@ fun VideoDetailScreen() {
54
63
LaunchedEffect (Unit ) {
55
64
viewModel.getDetail(acContent)
56
65
}
66
+
57
67
Box (modifier = Modifier .fillMaxSize().background(Color .White ).noRippleClickable {
58
68
screenNavigation.cancelPopupWindow()
59
69
}) {
@@ -74,7 +84,9 @@ fun VideoDetailScreen() {
74
84
is ScreenResult .Success -> {
75
85
val vc = videoContent.invoke()
76
86
Box (contentAlignment = Alignment .Center ) {
77
- videoDetailContent(acContent, vc, viewModel)
87
+ videoDetailContent(
88
+ acContent, vc, viewModel, viewModel.userSpaceFocusParent, viewModel.episodeFocusParent
89
+ )
78
90
79
91
val showPlayerLoading = remember { mutableStateOf(false ) }
80
92
if (showPlayerLoading.value) {
@@ -113,7 +125,13 @@ fun VideoDetailScreen() {
113
125
}
114
126
115
127
@Composable
116
- fun videoDetailContent (acContent : AcContent , vContent : VideoContent , viewModel : VideoDetailViewModel ) {
128
+ fun videoDetailContent (
129
+ acContent : AcContent ,
130
+ vContent : VideoContent ,
131
+ viewModel : VideoDetailViewModel ,
132
+ userSpaceFocusParent : FocusRequester ,
133
+ episodeFocusParent : FocusRequester
134
+ ) {
117
135
val screenNavigation = LocalScreenNavigator .current
118
136
Column (modifier = Modifier .fillMaxSize().padding(24 .dp, 48 .dp, 24 .dp, 48 .dp)) {
119
137
Row (verticalAlignment = Alignment .CenterVertically ) {
@@ -131,18 +149,50 @@ fun videoDetailContent(acContent: AcContent, vContent: VideoContent, viewModel:
131
149
)
132
150
}
133
151
Spacer (modifier = Modifier .width(16 .dp))
134
- Text (
135
- modifier = Modifier .noRippleClickable {
136
- screenNavigation.push(
137
- Manifest .UserSpaceScreen ,
138
- screenKey = vContent.user?.id.orEmpty(),
139
- arguments = ScreenArgs .putValue(" KEY_USER_NAME" , vContent.user?.name)
140
- .putValue(" KEY_USER_ID" , vContent.user?.id),
141
- pushOptions = PushOptions (
142
- removePredicate = PushOptions .RemoveAnyPredicate (Manifest .UserSpaceScreen )
143
- )
152
+ val isFocused = remember { mutableStateOf(false ) }
153
+ val userSpaceFocusNode = remember { FocusNode (tag = " UserSpace" ) }
154
+ SideEffect {
155
+ if (viewModel.currentFocusNode.value == userSpaceFocusNode) {
156
+ userSpaceFocusParent.requestFocus()
157
+ }
158
+ }
159
+ Text (modifier = Modifier .onFocusParent(userSpaceFocusParent, " video detail - user space" ) {
160
+ isFocused.value = (it.isFocused)
161
+ if (it.isFocused) {
162
+ viewModel.currentFocusNode.value = userSpaceFocusNode
163
+ }
164
+ }.onFocusKeyEventMove(leftCanMove = notAllowMove,
165
+ rightCanMove = notAllowMove,
166
+ upCanMove = notAllowMove,
167
+ downCanMove = {
168
+ viewModel.currentFocusNode.value = FocusNode (tag = " Episode" )
169
+ episodeFocusParent.requestFocus()
170
+ false
171
+ }).noRippleClickable {
172
+ screenNavigation.push(
173
+ Manifest .UserSpaceScreen ,
174
+ screenKey = vContent.user?.id.orEmpty(),
175
+ arguments = ScreenArgs .putValue(" KEY_USER_NAME" , vContent.user?.name)
176
+ .putValue(" KEY_USER_ID" , vContent.user?.id),
177
+ pushOptions = PushOptions (
178
+ removePredicate = PushOptions .RemoveAnyPredicate (Manifest .UserSpaceScreen )
144
179
)
145
- }, text = vContent.user?.name.orEmpty(), fontSize = 20 .sp, color = ColorResource .acRed
180
+ )
181
+ userSpaceFocusParent.requestFocus()
182
+ }.background(
183
+ if (isFocused.value) {
184
+ ColorResource .acRed
185
+ } else {
186
+ Color .Transparent
187
+ }, shape = RoundedCornerShape (6 .dp)
188
+ ).padding(12 .dp, 6 .dp),
189
+ text = vContent.user?.name.orEmpty(),
190
+ fontSize = 20 .sp,
191
+ color = if (isFocused.value) {
192
+ Color .White
193
+ } else {
194
+ ColorResource .acRed
195
+ }
146
196
)
147
197
Spacer (modifier = Modifier .width(16 .dp))
148
198
Column {
@@ -216,28 +266,77 @@ fun videoDetailContent(acContent: AcContent, vContent: VideoContent, viewModel:
216
266
}
217
267
val isExpandedScreen = rememberIsWindowExpanded()
218
268
269
+ val focusVm = viewModel(GlobalFocusViewModel ::class ) {
270
+ GlobalFocusViewModel ()
271
+ }
272
+ LaunchedEffect (Unit ) {
273
+ if (focusVm.curFocusRequesterParent.value == null ) {
274
+ viewModel.episodeFocusParent.requestFocus()
275
+ }
276
+ }
277
+
278
+ val grid = remember {
279
+ if (isExpandedScreen) {
280
+ 6
281
+ } else {
282
+ 2
283
+ }
284
+ }
219
285
LazyVerticalGrid (
220
- columns = GridCells .Fixed (
221
- if (isExpandedScreen) {
222
- 6
223
- } else {
224
- 2
225
- }
226
- ),
227
- modifier = Modifier .fillMaxSize(),
286
+ columns = GridCells .Fixed (grid),
287
+ modifier = Modifier .fillMaxSize().onFocusParent(episodeFocusParent, " video detail - episode" )
288
+ .onFocusKeyEventMove(upCanMove = {
289
+ if (viewModel.currentFocusNode.value.index < grid) {
290
+ viewModel.currentFocusNode.value = FocusNode (tag = " UserSpace" )
291
+ userSpaceFocusParent.requestFocus()
292
+ }
293
+ true
294
+ }),
228
295
contentPadding = PaddingValues (top = 12 .dp, bottom = 12 .dp),
229
296
verticalArrangement = Arrangement .spacedBy(12 .dp),
230
297
horizontalArrangement = Arrangement .spacedBy(12 .dp)
231
298
) {
232
299
itemsIndexed(vContent.videoList.orEmpty()) { index, item ->
300
+
301
+ val focusRequester = remember { FocusRequester () }
302
+ val episodeFocusNode = remember { FocusNode (tag = " Episode" , index = index) }
303
+
304
+ val curParentFocused = focusVm.curFocusRequesterParent.collectAsState()
305
+ if (curParentFocused.value == episodeFocusParent && (viewModel.currentFocusNode.value == episodeFocusNode)) {
306
+ SideEffect {
307
+ focusRequester.requestFocus()
308
+ }
309
+ }
310
+
311
+ val isFocused = viewModel.currentFocusNode.collectAsState().value == episodeFocusNode
312
+
233
313
Box (
234
- modifier = Modifier .weight(1f ).height(52 .dp).clip(MaterialTheme .shapes.medium)
235
- .background(ColorResource .background)
314
+ modifier = Modifier .weight(1f ).height(52 .dp).clip(MaterialTheme .shapes.medium).background(
315
+ if (isFocused) {
316
+ ColorResource .acRed
317
+ } else {
318
+ ColorResource .background
319
+ }
320
+ )
236
321
) {
237
- Box (modifier = Modifier .align(Alignment .Center ).noRippleClickable {
238
- viewModel.play(acContent, index + 1 )
239
- }, contentAlignment = Alignment .Center ) {
240
- Text (text = (index + 1 ).toString(), style = MaterialTheme .typography.h3)
322
+ Box (modifier = Modifier .align(Alignment .Center ).fillMaxSize().padding(0 .dp, 0 .dp, 32 .dp, 0 .dp)
323
+ .focusRequester(focusRequester).onFocusChanged {
324
+ if (it.isFocused) {
325
+ viewModel.currentFocusNode.value = FocusNode (tag = " Episode" , index = index)
326
+ }
327
+ }.focusTarget().noRippleClickable {
328
+ viewModel.play(acContent, index + 1 )
329
+ }, contentAlignment = Alignment .Center
330
+ ) {
331
+ Text (
332
+ text = (index + 1 ).toString(),
333
+ style = MaterialTheme .typography.h3,
334
+ color = if (isFocused) {
335
+ ColorResource .white
336
+ } else {
337
+ ColorResource .black
338
+ }
339
+ )
241
340
}
242
341
PopupWindowLayout (modifier = Modifier .align(Alignment .CenterEnd ), displayContent = {
243
342
Card (
0 commit comments