Skip to content

Commit 1b305ec

Browse files
committed
Fix focus being lost on back press
1 parent bd4d813 commit 1b305ec

File tree

1 file changed

+75
-43
lines changed

1 file changed

+75
-43
lines changed

android/lib/tv/src/main/kotlin/net/mullvad/mullvadvpn/lib/tv/NavigationDrawerTv.kt

+75-43
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,21 @@ import androidx.compose.material3.Icon
2222
import androidx.compose.material3.MaterialTheme
2323
import androidx.compose.material3.Text
2424
import androidx.compose.runtime.Composable
25+
import androidx.compose.runtime.remember
2526
import androidx.compose.ui.Alignment
27+
import androidx.compose.ui.ExperimentalComposeUiApi
2628
import androidx.compose.ui.Modifier
29+
import androidx.compose.ui.focus.FocusDirection
30+
import androidx.compose.ui.focus.FocusRequester
31+
import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
32+
import androidx.compose.ui.focus.FocusRequester.Companion.Default
33+
import androidx.compose.ui.focus.focusProperties
34+
import androidx.compose.ui.focus.focusRequester
35+
import androidx.compose.ui.focus.onFocusChanged
2736
import androidx.compose.ui.graphics.Brush
2837
import androidx.compose.ui.graphics.Color
38+
import androidx.compose.ui.graphics.vector.ImageVector
39+
import androidx.compose.ui.platform.LocalFocusManager
2940
import androidx.compose.ui.res.painterResource
3041
import androidx.compose.ui.res.pluralStringResource
3142
import androidx.compose.ui.res.stringResource
@@ -38,6 +49,7 @@ import androidx.tv.material3.DrawerValue
3849
import androidx.tv.material3.ModalNavigationDrawer
3950
import androidx.tv.material3.NavigationDrawerItem
4051
import androidx.tv.material3.NavigationDrawerItemDefaults
52+
import androidx.tv.material3.NavigationDrawerScope
4153
import androidx.tv.material3.rememberDrawerState
4254
import net.mullvad.mullvadvpn.lib.theme.AppTheme
4355

@@ -62,6 +74,7 @@ fun PreviewNavigationDrawerTvClosed(
6274
}
6375
}
6476

77+
@OptIn(ExperimentalComposeUiApi::class)
6578
@Composable
6679
@Suppress("LongMethod")
6780
fun NavigationDrawerTv(
@@ -73,77 +86,96 @@ fun NavigationDrawerTv(
7386
content: @Composable () -> Unit,
7487
) {
7588
val drawerState = rememberDrawerState(initialDrawerValue)
89+
val focusRequester = remember { FocusRequester() }
90+
val brush = remember { Brush.horizontalGradient(listOf(Color.Black, Color.Transparent)) }
91+
92+
val focusManager = LocalFocusManager.current
93+
7694
if (drawerState.currentValue == DrawerValue.Open) {
77-
BackHandler(onBack = { drawerState.setValue(DrawerValue.Closed) })
95+
BackHandler(
96+
onBack = {
97+
drawerState.setValue(DrawerValue.Closed)
98+
focusManager.moveFocus(FocusDirection.Right)
99+
}
100+
)
78101
}
79-
val brush = Brush.horizontalGradient(listOf(Color.Black, Color.Transparent))
80102

81103
ModalNavigationDrawer(
104+
modifier =
105+
Modifier.focusRequester(focusRequester).focusProperties {
106+
enter = { if (focusRequester.restoreFocusedChild()) Cancel else Default }
107+
},
82108
drawerState = drawerState,
83109
scrimBrush = brush,
84110
drawerContent = {
85-
val animatedPadding = animateDpAsState(if (hasFocus) 20.dp else 16.dp)
86111
Box(
87112
Modifier.fillMaxHeight()
88113
.background(brush)
89114
.padding(top = 24.dp, bottom = 24.dp, start = 12.dp, end = 12.dp)
90115
.selectableGroup()
91116
) {
117+
val animatedPadding = animateDpAsState(if (hasFocus) 16.dp else 12.dp)
118+
92119
NavigationDrawerTvHeader(
93120
modifier =
94121
Modifier.align(Alignment.TopStart).padding(start = animatedPadding.value),
95122
isExpanded = hasFocus,
96123
daysLeftUntilExpiry = daysLeftUntilExpiry,
97124
deviceName = deviceName,
98125
)
99-
100-
NavigationDrawerItem(
101-
modifier = Modifier.align(Alignment.CenterStart),
126+
DrawerItemTv(
127+
modifier =
128+
Modifier.align(Alignment.CenterStart).onFocusChanged {
129+
focusRequester.saveFocusedChild()
130+
},
131+
icon = Icons.Default.AccountCircle,
132+
text = stringResource(R.string.settings_account),
102133
onClick = onAccountClick,
103-
selected = false,
104-
leadingContent = {
105-
Icon(
106-
tint = MaterialTheme.colorScheme.onPrimary,
107-
imageVector = Icons.Default.AccountCircle,
108-
contentDescription = null,
109-
)
110-
},
111-
) {
112-
Text(
113-
modifier = Modifier.fillMaxWidth(),
114-
color = MaterialTheme.colorScheme.onPrimary,
115-
text = "Account",
116-
maxLines = 1,
117-
overflow = TextOverflow.Clip,
118-
)
119-
}
120-
121-
NavigationDrawerItem(
122-
modifier = Modifier.align(Alignment.BottomStart),
134+
)
135+
DrawerItemTv(
136+
modifier =
137+
Modifier.align(Alignment.BottomStart).onFocusChanged {
138+
focusRequester.saveFocusedChild()
139+
},
140+
icon = Icons.Default.Settings,
141+
text = stringResource(R.string.settings),
123142
onClick = onSettingsClick,
124-
selected = false,
125-
leadingContent = {
126-
Icon(
127-
tint = MaterialTheme.colorScheme.onPrimary,
128-
imageVector = Icons.Default.Settings,
129-
contentDescription = null,
130-
)
131-
},
132-
) {
133-
Text(
134-
modifier = Modifier.fillMaxWidth(),
135-
color = MaterialTheme.colorScheme.onPrimary,
136-
text = "Settings",
137-
maxLines = 1,
138-
overflow = TextOverflow.Clip,
139-
)
140-
}
143+
)
141144
}
142145
},
143146
content = content,
144147
)
145148
}
146149

150+
@Composable
151+
private fun NavigationDrawerScope.DrawerItemTv(
152+
modifier: Modifier = Modifier,
153+
icon: ImageVector,
154+
text: String,
155+
onClick: () -> Unit,
156+
) {
157+
NavigationDrawerItem(
158+
modifier = modifier,
159+
onClick = onClick,
160+
selected = false,
161+
leadingContent = {
162+
Icon(
163+
tint = MaterialTheme.colorScheme.onPrimary,
164+
imageVector = icon,
165+
contentDescription = null,
166+
)
167+
},
168+
) {
169+
Text(
170+
modifier = Modifier.fillMaxWidth(),
171+
color = MaterialTheme.colorScheme.onPrimary,
172+
text = text,
173+
maxLines = 1,
174+
overflow = TextOverflow.Clip,
175+
)
176+
}
177+
}
178+
147179
@Composable
148180
private fun NavigationDrawerTvHeader(
149181
modifier: Modifier = Modifier,

0 commit comments

Comments
 (0)