@@ -22,10 +22,21 @@ import androidx.compose.material3.Icon
22
22
import androidx.compose.material3.MaterialTheme
23
23
import androidx.compose.material3.Text
24
24
import androidx.compose.runtime.Composable
25
+ import androidx.compose.runtime.remember
25
26
import androidx.compose.ui.Alignment
27
+ import androidx.compose.ui.ExperimentalComposeUiApi
26
28
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
27
36
import androidx.compose.ui.graphics.Brush
28
37
import androidx.compose.ui.graphics.Color
38
+ import androidx.compose.ui.graphics.vector.ImageVector
39
+ import androidx.compose.ui.platform.LocalFocusManager
29
40
import androidx.compose.ui.res.painterResource
30
41
import androidx.compose.ui.res.pluralStringResource
31
42
import androidx.compose.ui.res.stringResource
@@ -38,6 +49,7 @@ import androidx.tv.material3.DrawerValue
38
49
import androidx.tv.material3.ModalNavigationDrawer
39
50
import androidx.tv.material3.NavigationDrawerItem
40
51
import androidx.tv.material3.NavigationDrawerItemDefaults
52
+ import androidx.tv.material3.NavigationDrawerScope
41
53
import androidx.tv.material3.rememberDrawerState
42
54
import net.mullvad.mullvadvpn.lib.theme.AppTheme
43
55
@@ -62,6 +74,7 @@ fun PreviewNavigationDrawerTvClosed(
62
74
}
63
75
}
64
76
77
+ @OptIn(ExperimentalComposeUiApi ::class )
65
78
@Composable
66
79
@Suppress(" LongMethod" )
67
80
fun NavigationDrawerTv (
@@ -73,77 +86,96 @@ fun NavigationDrawerTv(
73
86
content : @Composable () -> Unit ,
74
87
) {
75
88
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
+
76
94
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
+ )
78
101
}
79
- val brush = Brush .horizontalGradient(listOf (Color .Black , Color .Transparent ))
80
102
81
103
ModalNavigationDrawer (
104
+ modifier =
105
+ Modifier .focusRequester(focusRequester).focusProperties {
106
+ enter = { if (focusRequester.restoreFocusedChild()) Cancel else Default }
107
+ },
82
108
drawerState = drawerState,
83
109
scrimBrush = brush,
84
110
drawerContent = {
85
- val animatedPadding = animateDpAsState(if (hasFocus) 20 .dp else 16 .dp)
86
111
Box (
87
112
Modifier .fillMaxHeight()
88
113
.background(brush)
89
114
.padding(top = 24 .dp, bottom = 24 .dp, start = 12 .dp, end = 12 .dp)
90
115
.selectableGroup()
91
116
) {
117
+ val animatedPadding = animateDpAsState(if (hasFocus) 16 .dp else 12 .dp)
118
+
92
119
NavigationDrawerTvHeader (
93
120
modifier =
94
121
Modifier .align(Alignment .TopStart ).padding(start = animatedPadding.value),
95
122
isExpanded = hasFocus,
96
123
daysLeftUntilExpiry = daysLeftUntilExpiry,
97
124
deviceName = deviceName,
98
125
)
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),
102
133
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),
123
142
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
+ )
141
144
}
142
145
},
143
146
content = content,
144
147
)
145
148
}
146
149
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
+
147
179
@Composable
148
180
private fun NavigationDrawerTvHeader (
149
181
modifier : Modifier = Modifier ,
0 commit comments