1
+ // Copyright (c) Tailscale Inc & AUTHORS
2
+ // SPDX-License-Identifier: BSD-3-Clause
3
+
4
+ package com.tailscale.ipn.ui.view
5
+
6
+ import androidx.compose.foundation.layout.Column
7
+ import androidx.compose.foundation.layout.Row
8
+ import androidx.compose.foundation.layout.Spacer
9
+ import androidx.compose.foundation.layout.height
10
+ import androidx.compose.foundation.layout.padding
11
+ import androidx.compose.foundation.layout.width
12
+ import androidx.compose.material.icons.Icons
13
+ import androidx.compose.material.icons.rounded.CheckCircle
14
+ import androidx.compose.material.icons.rounded.Warning
15
+ import androidx.compose.material3.Button
16
+ import androidx.compose.material3.ButtonDefaults
17
+ import androidx.compose.material3.Icon
18
+ import androidx.compose.material3.MaterialTheme
19
+ import androidx.compose.material3.Text
20
+ import androidx.compose.material3.TextField
21
+ import androidx.compose.runtime.Composable
22
+ import androidx.compose.runtime.LaunchedEffect
23
+ import androidx.compose.runtime.collectAsState
24
+ import androidx.compose.runtime.getValue
25
+ import androidx.compose.runtime.remember
26
+ import androidx.compose.runtime.snapshotFlow
27
+ import androidx.compose.ui.Alignment
28
+ import androidx.compose.ui.Modifier
29
+ import androidx.compose.ui.focus.FocusRequester
30
+ import androidx.compose.ui.focus.focusRequester
31
+ import androidx.compose.ui.platform.LocalWindowInfo
32
+ import androidx.compose.ui.res.stringResource
33
+ import androidx.compose.ui.unit.dp
34
+ import com.tailscale.ipn.R
35
+ import kotlinx.coroutines.flow.MutableStateFlow
36
+ import kotlinx.coroutines.flow.StateFlow
37
+
38
+ /* *
39
+ * EditSubnetRouteDialogView is the content of the dialog that allows the user to add or edit a subnet route.
40
+ */
41
+ @Composable
42
+ fun EditSubnetRouteDialogView (
43
+ valueFlow : MutableStateFlow <String >,
44
+ isValueValidFlow : StateFlow <Boolean >,
45
+ onValueChange : (String ) -> Unit ,
46
+ onCommit : (String ) -> Unit ,
47
+ onCancel : () -> Unit
48
+ ) {
49
+ val value by valueFlow.collectAsState()
50
+ val isValueValid by isValueValidFlow.collectAsState()
51
+ val focusRequester = remember { FocusRequester () }
52
+
53
+ Column (
54
+ modifier = Modifier .padding(16 .dp),
55
+ ) {
56
+ Text (text = stringResource(R .string.enter_valid_route))
57
+
58
+ Text (
59
+ text = stringResource(R .string.route_help_text),
60
+ color = MaterialTheme .colorScheme.secondary,
61
+ fontSize = MaterialTheme .typography.bodySmall.fontSize
62
+ )
63
+
64
+ Spacer (modifier = Modifier .height(8 .dp))
65
+
66
+ TextField (
67
+ value = value,
68
+ onValueChange = { onValueChange(it) },
69
+ singleLine = true ,
70
+ isError = ! isValueValid,
71
+ modifier = Modifier .focusRequester(focusRequester)
72
+ )
73
+
74
+ Spacer (modifier = Modifier .height(8 .dp))
75
+
76
+ Row (
77
+ modifier = Modifier .align(Alignment .End )
78
+ ) {
79
+ Button (colors = ButtonDefaults .outlinedButtonColors(), onClick = {
80
+ onCancel()
81
+ }) {
82
+ Text (stringResource(R .string.cancel))
83
+ }
84
+
85
+ Spacer (modifier = Modifier .width(8 .dp))
86
+
87
+ Button (onClick = {
88
+ onCommit(value)
89
+ }, enabled = value.isNotEmpty() && isValueValid) {
90
+ Text (stringResource(R .string.ok))
91
+ }
92
+ }
93
+ }
94
+
95
+ // When the dialog is opened, focus on the text field to present the keyboard auto-magically.
96
+ val windowInfo = LocalWindowInfo .current
97
+ LaunchedEffect (windowInfo) {
98
+ snapshotFlow { windowInfo.isWindowFocused }.collect { isWindowFocused ->
99
+ if (isWindowFocused) {
100
+ focusRequester.requestFocus()
101
+ }
102
+ }
103
+ }
104
+ }
0 commit comments