@@ -13,6 +13,10 @@ import androidx.compose.runtime.*
1313import androidx.compose.ui.Alignment
1414import androidx.compose.ui.Modifier
1515import 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
1620import androidx.compose.ui.graphics.Color
1721import androidx.compose.ui.unit.dp
1822import androidx.compose.ui.unit.sp
@@ -23,11 +27,16 @@ import org.succlz123.app.acfun.base.AcBackButton
2327import org.succlz123.app.acfun.base.LoadingFailView
2428import org.succlz123.app.acfun.base.LoadingView
2529import org.succlz123.app.acfun.theme.ColorResource
30+ import org.succlz123.app.acfun.ui.main.GlobalFocusViewModel
2631import org.succlz123.lib.click.noRippleClickable
2732import org.succlz123.lib.common.getPlatformName
2833import org.succlz123.lib.filedownloader.core.DownloadRequest
2934import org.succlz123.lib.filedownloader.core.DownloadStateType
3035import 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
3140import org.succlz123.lib.image.AsyncImageUrlMultiPlatform
3241import org.succlz123.lib.screen.LocalScreenNavigator
3342import org.succlz123.lib.screen.LocalScreenRecord
@@ -54,6 +63,7 @@ fun VideoDetailScreen() {
5463 LaunchedEffect (Unit ) {
5564 viewModel.getDetail(acContent)
5665 }
66+
5767 Box (modifier = Modifier .fillMaxSize().background(Color .White ).noRippleClickable {
5868 screenNavigation.cancelPopupWindow()
5969 }) {
@@ -74,7 +84,9 @@ fun VideoDetailScreen() {
7484 is ScreenResult .Success -> {
7585 val vc = videoContent.invoke()
7686 Box (contentAlignment = Alignment .Center ) {
77- videoDetailContent(acContent, vc, viewModel)
87+ videoDetailContent(
88+ acContent, vc, viewModel, viewModel.userSpaceFocusParent, viewModel.episodeFocusParent
89+ )
7890
7991 val showPlayerLoading = remember { mutableStateOf(false ) }
8092 if (showPlayerLoading.value) {
@@ -113,7 +125,13 @@ fun VideoDetailScreen() {
113125}
114126
115127@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+ ) {
117135 val screenNavigation = LocalScreenNavigator .current
118136 Column (modifier = Modifier .fillMaxSize().padding(24 .dp, 48 .dp, 24 .dp, 48 .dp)) {
119137 Row (verticalAlignment = Alignment .CenterVertically ) {
@@ -131,18 +149,50 @@ fun videoDetailContent(acContent: AcContent, vContent: VideoContent, viewModel:
131149 )
132150 }
133151 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 )
144179 )
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+ }
146196 )
147197 Spacer (modifier = Modifier .width(16 .dp))
148198 Column {
@@ -216,28 +266,77 @@ fun videoDetailContent(acContent: AcContent, vContent: VideoContent, viewModel:
216266 }
217267 val isExpandedScreen = rememberIsWindowExpanded()
218268
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+ }
219285 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+ }),
228295 contentPadding = PaddingValues (top = 12 .dp, bottom = 12 .dp),
229296 verticalArrangement = Arrangement .spacedBy(12 .dp),
230297 horizontalArrangement = Arrangement .spacedBy(12 .dp)
231298 ) {
232299 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+
233313 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+ )
236321 ) {
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+ )
241340 }
242341 PopupWindowLayout (modifier = Modifier .align(Alignment .CenterEnd ), displayContent = {
243342 Card (
0 commit comments