Skip to content

Commit

Permalink
ui: add ability to advertise Android device as subnet router (#595)
Browse files Browse the repository at this point in the history
  • Loading branch information
agottardo authored Jan 17, 2025
1 parent a2850b1 commit 9c3378d
Show file tree
Hide file tree
Showing 11 changed files with 721 additions and 142 deletions.
3 changes: 3 additions & 0 deletions android/src/main/java/com/tailscale/ipn/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import com.tailscale.ipn.ui.view.RunExitNodeView
import com.tailscale.ipn.ui.view.SearchView
import com.tailscale.ipn.ui.view.SettingsView
import com.tailscale.ipn.ui.view.SplitTunnelAppPickerView
import com.tailscale.ipn.ui.view.SubnetRoutingView
import com.tailscale.ipn.ui.view.TailnetLockSetupView
import com.tailscale.ipn.ui.view.UserSwitcherNav
import com.tailscale.ipn.ui.view.UserSwitcherView
Expand Down Expand Up @@ -185,6 +186,7 @@ class MainActivity : ComponentActivity() {
onNavigateToDNSSettings = { navController.navigate("dnsSettings") },
onNavigateToSplitTunneling = { navController.navigate("splitTunneling") },
onNavigateToTailnetLock = { navController.navigate("tailnetLock") },
onNavigateToSubnetRouting = { navController.navigate("subnetRouting")},
onNavigateToMDMSettings = { navController.navigate("mdmSettings") },
onNavigateToManagedBy = { navController.navigate("managedBy") },
onNavigateToUserSwitcher = { navController.navigate("userSwitcher") },
Expand Down Expand Up @@ -247,6 +249,7 @@ class MainActivity : ComponentActivity() {
composable("dnsSettings") { DNSSettingsView(backTo("settings")) }
composable("splitTunneling") { SplitTunnelAppPickerView(backTo("settings")) }
composable("tailnetLock") { TailnetLockSetupView(backTo("settings")) }
composable("subnetRouting") { SubnetRoutingView(backTo("settings")) }
composable("about") { AboutView(backTo("settings")) }
composable("mdmSettings") { MDMSettingsDebugView(backTo("settings")) }
composable("managedBy") { ManagedByView(backTo("settings")) }
Expand Down
1 change: 1 addition & 0 deletions android/src/main/java/com/tailscale/ipn/ui/Links.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ object Links {
const val SUPPORT_URL = "https://tailscale.com/contact/support#support-form"
const val TAILDROP_KB_URL = "https://tailscale.com/kb/1106/taildrop"
const val TAILFS_KB_URL = "https://tailscale.com/kb/1106/taildrop"
const val SUBNET_ROUTERS_KB_URL = "https://tailscale.com/kb/1019/subnets"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause

package com.tailscale.ipn.ui.view

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Warning
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.tailscale.ipn.R
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

/**
* EditSubnetRouteDialogView is the content of the dialog that allows the user to add or edit a subnet route.
*/
@Composable
fun EditSubnetRouteDialogView(
valueFlow: MutableStateFlow<String>,
isValueValidFlow: StateFlow<Boolean>,
onValueChange: (String) -> Unit,
onCommit: (String) -> Unit,
onCancel: () -> Unit
) {
val value by valueFlow.collectAsState()
val isValueValid by isValueValidFlow.collectAsState()
val focusRequester = remember { FocusRequester() }

Column(
modifier = Modifier.padding(16.dp),
) {
Text(text = stringResource(R.string.enter_valid_route))

Text(
text = stringResource(R.string.route_help_text),
color = MaterialTheme.colorScheme.secondary,
fontSize = MaterialTheme.typography.bodySmall.fontSize
)

Spacer(modifier = Modifier.height(8.dp))

TextField(
value = value,
onValueChange = { onValueChange(it) },
singleLine = true,
isError = !isValueValid,
modifier = Modifier.focusRequester(focusRequester)
)

Spacer(modifier = Modifier.height(8.dp))

Row(
modifier = Modifier.align(Alignment.End)
) {
Button(colors = ButtonDefaults.outlinedButtonColors(), onClick = {
onCancel()
}) {
Text(stringResource(R.string.cancel))
}

Spacer(modifier = Modifier.width(8.dp))

Button(onClick = {
onCommit(value)
}, enabled = value.isNotEmpty() && isValueValid) {
Text(stringResource(R.string.ok))
}
}
}

// When the dialog is opened, focus on the text field to present the keyboard auto-magically.
val windowInfo = LocalWindowInfo.current
LaunchedEffect(windowInfo) {
snapshotFlow { windowInfo.isWindowFocused }.collect { isWindowFocused ->
if (isWindowFocused) {
focusRequester.requestFocus()
}
}
}
}
Loading

0 comments on commit 9c3378d

Please sign in to comment.