Skip to content

Commit e258dcc

Browse files
committed
🎨 防止焦点导致崩溃,部分列表循环
1 parent 027c018 commit e258dcc

File tree

8 files changed

+129
-41
lines changed

8 files changed

+129
-41
lines changed

tv/src/main/java/top/yogiczy/mytv/tv/ui/material/Popup.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import androidx.compose.ui.input.pointer.pointerInput
1919
import androidx.tv.material3.MaterialTheme
2020
import top.yogiczy.mytv.tv.ui.utils.captureBackKey
2121
import top.yogiczy.mytv.tv.ui.utils.ifElse
22+
import top.yogiczy.mytv.tv.ui.utils.saveRequestFocus
2223
import java.util.UUID
2324

2425
class PopupManager {
@@ -57,7 +58,7 @@ fun Modifier.popupable() = composed {
5758
val focusRequester = remember { FocusRequester() }
5859

5960
DisposableEffect(Unit) {
60-
focusRequester.requestFocus()
61+
focusRequester.saveRequestFocus()
6162
popupManager.push(focusRequester)
6263
onDispose { popupManager.pop() }
6364
}

tv/src/main/java/top/yogiczy/mytv/tv/ui/screens/channel/components/ChannelItem.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import top.yogiczy.mytv.core.data.entities.epg.EpgProgrammeRecent
3636
import top.yogiczy.mytv.tv.ui.theme.MyTVTheme
3737
import top.yogiczy.mytv.tv.ui.utils.handleKeyEvents
3838
import top.yogiczy.mytv.tv.ui.utils.ifElse
39+
import top.yogiczy.mytv.tv.ui.utils.saveRequestFocus
3940

4041
@Composable
4142
fun ChannelItem(
@@ -57,7 +58,7 @@ fun ChannelItem(
5758
LaunchedEffect(Unit) {
5859
if (initialFocused) {
5960
onInitialFocused()
60-
focusRequester.requestFocus()
61+
focusRequester.saveRequestFocus()
6162
}
6263
}
6364

tv/src/main/java/top/yogiczy/mytv/tv/ui/screens/classicchannel/components/ClassicChannelGroupItemList.kt

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ import androidx.compose.runtime.derivedStateOf
1616
import androidx.compose.runtime.getValue
1717
import androidx.compose.runtime.mutableStateOf
1818
import androidx.compose.runtime.remember
19+
import androidx.compose.runtime.rememberCoroutineScope
1920
import androidx.compose.runtime.setValue
2021
import androidx.compose.runtime.snapshotFlow
2122
import androidx.compose.ui.ExperimentalComposeUiApi
2223
import androidx.compose.ui.Modifier
2324
import androidx.compose.ui.focus.FocusRequester
2425
import androidx.compose.ui.focus.focusRequester
25-
import androidx.compose.ui.focus.focusRestorer
2626
import androidx.compose.ui.focus.onFocusChanged
2727
import androidx.compose.ui.text.style.TextAlign
2828
import androidx.compose.ui.tooling.preview.Preview
@@ -32,6 +32,7 @@ import androidx.tv.material3.ListItemDefaults
3232
import androidx.tv.material3.MaterialTheme
3333
import androidx.tv.material3.Text
3434
import kotlinx.coroutines.flow.distinctUntilChanged
35+
import kotlinx.coroutines.launch
3536
import top.yogiczy.mytv.core.data.entities.channel.ChannelGroup
3637
import top.yogiczy.mytv.core.data.entities.channel.ChannelGroupList
3738
import top.yogiczy.mytv.tv.ui.material.rememberDebounceState
@@ -40,6 +41,8 @@ import top.yogiczy.mytv.tv.ui.theme.MyTVTheme
4041
import top.yogiczy.mytv.tv.ui.utils.focusOnLaunchedSaveable
4142
import top.yogiczy.mytv.tv.ui.utils.handleKeyEvents
4243
import top.yogiczy.mytv.tv.ui.utils.ifElse
44+
import top.yogiczy.mytv.tv.ui.utils.saveFocusRestorer
45+
import top.yogiczy.mytv.tv.ui.utils.saveRequestFocus
4346
import kotlin.math.max
4447

4548
@OptIn(ExperimentalComposeUiApi::class)
@@ -68,14 +71,31 @@ fun ClassicChannelGroupItemList(
6871
onChannelGroupFocused(focusedChannelGroup)
6972
}
7073

74+
val coroutineScope = rememberCoroutineScope()
75+
val firstFocusRequester = remember { FocusRequester() }
76+
val lastFocusRequester = remember { FocusRequester() }
77+
fun scrollToFirst() {
78+
coroutineScope.launch {
79+
listState.scrollToItem(0)
80+
firstFocusRequester.saveRequestFocus()
81+
}
82+
}
83+
84+
fun scrollToLast() {
85+
coroutineScope.launch {
86+
listState.scrollToItem(channelGroupList.lastIndex)
87+
lastFocusRequester.saveRequestFocus()
88+
}
89+
}
90+
7191
LazyColumn(
7292
modifier = modifier
7393
.width(140.dp)
7494
.fillMaxHeight()
7595
.background(MaterialTheme.colorScheme.surface.copy(0.9f))
7696
.ifElse(
7797
LocalSettings.current.uiFocusOptimize,
78-
Modifier.focusRestorer {
98+
Modifier.saveFocusRestorer {
7999
itemFocusRequesterList[channelGroupList.indexOf(focusedChannelGroup)]
80100
},
81101
),
@@ -89,7 +109,19 @@ fun ClassicChannelGroupItemList(
89109
ClassicChannelGroupItem(
90110
modifier = Modifier
91111
.ifElse(channelGroup == initialChannelGroup, Modifier.focusOnLaunchedSaveable())
92-
.focusRequester(itemFocusRequesterList[index]),
112+
.focusRequester(itemFocusRequesterList[index])
113+
.ifElse(
114+
index == 0,
115+
Modifier
116+
.focusRequester(firstFocusRequester)
117+
.handleKeyEvents(onUp = { scrollToLast() })
118+
)
119+
.ifElse(
120+
index == channelGroupList.lastIndex,
121+
Modifier
122+
.focusRequester(lastFocusRequester)
123+
.handleKeyEvents(onDown = { scrollToFirst() })
124+
),
93125
channelGroupProvider = { channelGroup },
94126
isSelectedProvider = { isSelected },
95127
onFocused = {

tv/src/main/java/top/yogiczy/mytv/tv/ui/screens/classicchannel/components/ClassicChannelItemList.kt

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import androidx.compose.runtime.derivedStateOf
1919
import androidx.compose.runtime.getValue
2020
import androidx.compose.runtime.mutableStateOf
2121
import androidx.compose.runtime.remember
22+
import androidx.compose.runtime.rememberCoroutineScope
2223
import androidx.compose.runtime.saveable.rememberSaveable
2324
import androidx.compose.runtime.setValue
2425
import androidx.compose.runtime.snapshotFlow
@@ -29,7 +30,6 @@ import androidx.compose.ui.draw.clip
2930
import androidx.compose.ui.focus.FocusDirection
3031
import androidx.compose.ui.focus.FocusRequester
3132
import androidx.compose.ui.focus.focusRequester
32-
import androidx.compose.ui.focus.focusRestorer
3333
import androidx.compose.ui.focus.onFocusChanged
3434
import androidx.compose.ui.platform.LocalFocusManager
3535
import androidx.compose.ui.text.style.TextOverflow
@@ -40,6 +40,7 @@ import androidx.tv.material3.ListItemDefaults
4040
import androidx.tv.material3.MaterialTheme
4141
import androidx.tv.material3.Text
4242
import kotlinx.coroutines.flow.distinctUntilChanged
43+
import kotlinx.coroutines.launch
4344
import top.yogiczy.mytv.core.data.entities.channel.Channel
4445
import top.yogiczy.mytv.core.data.entities.channel.ChannelGroup
4546
import top.yogiczy.mytv.core.data.entities.channel.ChannelList
@@ -53,6 +54,8 @@ import top.yogiczy.mytv.tv.ui.screens.settings.LocalSettings
5354
import top.yogiczy.mytv.tv.ui.theme.MyTVTheme
5455
import top.yogiczy.mytv.tv.ui.utils.handleKeyEvents
5556
import top.yogiczy.mytv.tv.ui.utils.ifElse
57+
import top.yogiczy.mytv.tv.ui.utils.saveFocusRestorer
58+
import top.yogiczy.mytv.tv.ui.utils.saveRequestFocus
5659
import kotlin.math.max
5760

5861
@OptIn(ExperimentalComposeUiApi::class)
@@ -100,14 +103,33 @@ fun ClassicChannelItemList(
100103
.collect { _ -> onUserAction() }
101104
}
102105

106+
val coroutineScope = rememberCoroutineScope()
107+
val firstFocusRequester = remember { FocusRequester() }
108+
val lastFocusRequester = remember { FocusRequester() }
109+
fun scrollToFirst() {
110+
coroutineScope.launch {
111+
listState.scrollToItem(0)
112+
firstFocusRequester.saveRequestFocus()
113+
}
114+
}
115+
116+
fun scrollToLast() {
117+
coroutineScope.launch {
118+
listState.scrollToItem(channelList.lastIndex)
119+
lastFocusRequester.saveRequestFocus()
120+
}
121+
}
122+
103123
LazyColumn(
104124
modifier = modifier
105125
.fillMaxHeight()
106126
.width(if (showChannelLogoProvider()) 280.dp else 220.dp)
107127
.background(MaterialTheme.colorScheme.surface.copy(0.8f))
108128
.ifElse(
109129
LocalSettings.current.uiFocusOptimize,
110-
Modifier.focusRestorer { itemFocusRequesterList[channelList.indexOf(focusedChannel)] },
130+
Modifier.saveFocusRestorer {
131+
itemFocusRequesterList[channelList.indexOf(focusedChannel)]
132+
},
111133
),
112134
state = listState,
113135
contentPadding = PaddingValues(8.dp),
@@ -120,6 +142,19 @@ fun ClassicChannelItemList(
120142
}
121143

122144
ClassicChannelItem(
145+
modifier = Modifier
146+
.ifElse(
147+
index == 0,
148+
Modifier
149+
.focusRequester(firstFocusRequester)
150+
.handleKeyEvents(onUp = { scrollToLast() })
151+
)
152+
.ifElse(
153+
index == channelList.lastIndex,
154+
Modifier
155+
.focusRequester(lastFocusRequester)
156+
.handleKeyEvents(onDown = { scrollToFirst() })
157+
),
123158
channelProvider = { channel },
124159
onChannelSelected = { onChannelSelected(channel) },
125160
onChannelFavoriteToggle = {
@@ -177,7 +212,7 @@ private fun ClassicChannelItem(
177212
LaunchedEffect(Unit) {
178213
if (initialFocusedProvider()) {
179214
onInitialFocused()
180-
focusRequester.requestFocus()
215+
focusRequester.saveRequestFocus()
181216
}
182217
}
183218

tv/src/main/java/top/yogiczy/mytv/tv/ui/screens/epg/components/EpgDayItemList.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import androidx.compose.ui.ExperimentalComposeUiApi
1313
import androidx.compose.ui.Modifier
1414
import androidx.compose.ui.focus.FocusRequester
1515
import androidx.compose.ui.focus.focusRequester
16-
import androidx.compose.ui.focus.focusRestorer
1716
import androidx.compose.ui.tooling.preview.Preview
1817
import androidx.compose.ui.unit.dp
1918
import kotlinx.collections.immutable.ImmutableList
@@ -22,6 +21,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
2221
import top.yogiczy.mytv.tv.ui.screens.settings.LocalSettings
2322
import top.yogiczy.mytv.tv.ui.theme.MyTVTheme
2423
import top.yogiczy.mytv.tv.ui.utils.ifElse
24+
import top.yogiczy.mytv.tv.ui.utils.saveFocusRestorer
2525
import kotlin.math.max
2626

2727
@OptIn(ExperimentalComposeUiApi::class)
@@ -48,7 +48,7 @@ fun EpgDayItemList(
4848
LazyColumn(
4949
modifier = modifier.ifElse(
5050
LocalSettings.current.uiFocusOptimize,
51-
Modifier.focusRestorer { itemFocusRequesterList[dayList.indexOf(currentDay)] },
51+
Modifier.saveFocusRestorer { itemFocusRequesterList[dayList.indexOf(currentDay)] },
5252
),
5353
state = listState,
5454
contentPadding = PaddingValues(vertical = 8.dp),

tv/src/main/java/top/yogiczy/mytv/tv/ui/screens/settings/components/SettingsCategoryList.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import androidx.compose.ui.Modifier
1616
import androidx.compose.ui.focus.FocusDirection
1717
import androidx.compose.ui.focus.FocusRequester
1818
import androidx.compose.ui.focus.focusRequester
19-
import androidx.compose.ui.focus.focusRestorer
2019
import androidx.compose.ui.focus.onFocusChanged
2120
import androidx.compose.ui.graphics.vector.ImageVector
2221
import androidx.compose.ui.platform.LocalFocusManager
@@ -34,6 +33,7 @@ import top.yogiczy.mytv.tv.ui.theme.MyTVTheme
3433
import top.yogiczy.mytv.tv.ui.utils.focusOnLaunchedSaveable
3534
import top.yogiczy.mytv.tv.ui.utils.handleKeyEvents
3635
import top.yogiczy.mytv.tv.ui.utils.ifElse
36+
import top.yogiczy.mytv.tv.ui.utils.saveFocusRestorer
3737

3838
@OptIn(ExperimentalComposeUiApi::class)
3939
@Composable
@@ -49,7 +49,7 @@ fun SettingsCategoryList(
4949
verticalArrangement = Arrangement.spacedBy(10.dp),
5050
modifier = modifier.ifElse(
5151
LocalSettings.current.uiFocusOptimize,
52-
Modifier.focusRestorer(),
52+
Modifier.saveFocusRestorer(),
5353
),
5454
) {
5555
itemsIndexed(SettingsCategories.entries) { index, category ->

tv/src/main/java/top/yogiczy/mytv/tv/ui/screens/settings/components/SettingsContentList.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,14 @@ import androidx.compose.foundation.lazy.LazyColumn
66
import androidx.compose.foundation.lazy.LazyListScope
77
import androidx.compose.runtime.Composable
88
import androidx.compose.runtime.remember
9-
import androidx.compose.ui.ExperimentalComposeUiApi
109
import androidx.compose.ui.Modifier
1110
import androidx.compose.ui.focus.FocusRequester
12-
import androidx.compose.ui.focus.focusRestorer
1311
import androidx.compose.ui.unit.dp
1412
import top.yogiczy.mytv.tv.ui.rememberChildPadding
1513
import top.yogiczy.mytv.tv.ui.screens.settings.LocalSettings
1614
import top.yogiczy.mytv.tv.ui.utils.ifElse
15+
import top.yogiczy.mytv.tv.ui.utils.saveFocusRestorer
1716

18-
@OptIn(ExperimentalComposeUiApi::class)
1917
@Composable
2018
fun SettingsContentList(
2119
modifier: Modifier = Modifier,
@@ -27,7 +25,7 @@ fun SettingsContentList(
2725
LazyColumn(
2826
modifier = modifier.ifElse(
2927
LocalSettings.current.uiFocusOptimize,
30-
Modifier.focusRestorer { firstItemFocusRequester },
28+
Modifier.saveFocusRestorer { firstItemFocusRequester },
3129
),
3230
verticalArrangement = Arrangement.spacedBy(10.dp),
3331
contentPadding = PaddingValues(top = 4.dp, bottom = childPadding.bottom),

0 commit comments

Comments
 (0)