This reverts commit 0d1a3cf415
.
Signed-off-by: Andrea Gottardo <andrea@gottardo.me>
pull/231/head
parent
e953b19189
commit
19adff3077
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package com.tailscale.ipn.ui.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.tailscale.ipn.R
|
||||||
|
import com.tailscale.ipn.ui.theme.ts_color_light_blue
|
||||||
|
import com.tailscale.ipn.ui.util.LoadingIndicator
|
||||||
|
import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav
|
||||||
|
import com.tailscale.ipn.ui.viewModel.RunExitNodeViewModel
|
||||||
|
import com.tailscale.ipn.ui.viewModel.RunExitNodeViewModelFactory
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RunExitNodeView(
|
||||||
|
nav: ExitNodePickerNav,
|
||||||
|
model: RunExitNodeViewModel = viewModel(factory = RunExitNodeViewModelFactory())
|
||||||
|
) {
|
||||||
|
val isRunningExitNode = model.isRunningExitNode.collectAsState().value
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = { Header(R.string.run_as_exit_node, onBack = nav.onNavigateToExitNodePicker) }) {
|
||||||
|
innerPadding ->
|
||||||
|
LoadingIndicator.Wrap {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement =
|
||||||
|
Arrangement.spacedBy(16.dp, alignment = Alignment.Top),
|
||||||
|
modifier = Modifier.padding(innerPadding).padding(16.dp).fillMaxHeight()) {
|
||||||
|
RunExitNodeGraphic()
|
||||||
|
|
||||||
|
if (isRunningExitNode) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.running_as_exit_node),
|
||||||
|
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
fontWeight = FontWeight.SemiBold)
|
||||||
|
Text(stringResource(R.string.run_exit_node_explainer_running))
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.run_this_device_as_an_exit_node),
|
||||||
|
fontFamily = MaterialTheme.typography.titleLarge.fontFamily,
|
||||||
|
fontSize = MaterialTheme.typography.titleLarge.fontSize,
|
||||||
|
fontWeight = FontWeight.SemiBold)
|
||||||
|
Text(stringResource(R.string.run_exit_node_explainer))
|
||||||
|
}
|
||||||
|
Text(stringResource(R.string.run_exit_node_caution), color = Color.Red)
|
||||||
|
|
||||||
|
PrimaryActionButton(onClick = { model.setRunningExitNode(!isRunningExitNode) }) {
|
||||||
|
if (isRunningExitNode) {
|
||||||
|
Text(stringResource(R.string.stop_running_as_exit_node))
|
||||||
|
} else {
|
||||||
|
Text(stringResource(R.string.start_running_as_exit_node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RunExitNodeGraphic() {
|
||||||
|
@Composable
|
||||||
|
fun ArrowForward() {
|
||||||
|
Icon(
|
||||||
|
Icons.AutoMirrored.Outlined.ArrowForward,
|
||||||
|
"Arrow Forward",
|
||||||
|
tint = MaterialTheme.colorScheme.secondary,
|
||||||
|
modifier = Modifier.size(24.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(vertical = 18.dp)) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.computer),
|
||||||
|
"Computer icon",
|
||||||
|
tint = ts_color_light_blue,
|
||||||
|
modifier = Modifier.size(36.dp))
|
||||||
|
ArrowForward()
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.android),
|
||||||
|
"Android icon",
|
||||||
|
tint = ts_color_light_blue,
|
||||||
|
modifier = Modifier.size(36.dp))
|
||||||
|
ArrowForward()
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.globe),
|
||||||
|
"Globe icon",
|
||||||
|
tint = ts_color_light_blue,
|
||||||
|
modifier = Modifier.size(36.dp))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package com.tailscale.ipn.ui.viewModel
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.tailscale.ipn.ui.localapi.Client
|
||||||
|
import com.tailscale.ipn.ui.model.Ipn
|
||||||
|
import com.tailscale.ipn.ui.notifier.Notifier
|
||||||
|
import com.tailscale.ipn.ui.util.LoadingIndicator
|
||||||
|
import com.tailscale.ipn.ui.util.set
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class RunExitNodeViewModelFactory() : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
return RunExitNodeViewModel() as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AdvertisedRoutesHelper() {
|
||||||
|
companion object {
|
||||||
|
fun exitNodeOnFromPrefs(prefs: Ipn.Prefs): Boolean {
|
||||||
|
var v4 = false
|
||||||
|
var v6 = false
|
||||||
|
prefs.AdvertiseRoutes?.forEach {
|
||||||
|
if (it == "0.0.0.0/0") {
|
||||||
|
v4 = true
|
||||||
|
}
|
||||||
|
if (it == "::/0") {
|
||||||
|
v6 = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v4 && v6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RunExitNodeViewModel() : IpnViewModel() {
|
||||||
|
|
||||||
|
val isRunningExitNode: StateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
var lastPrefs: Ipn.Prefs? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
Notifier.prefs.stateIn(viewModelScope).collect { prefs ->
|
||||||
|
Log.d("RunExitNode", "prefs: AdvertiseRoutes=" + prefs?.AdvertiseRoutes.toString())
|
||||||
|
prefs?.let {
|
||||||
|
lastPrefs = it
|
||||||
|
isRunningExitNode.set(AdvertisedRoutesHelper.exitNodeOnFromPrefs(it))
|
||||||
|
} ?: run { isRunningExitNode.set(false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setRunningExitNode(isOn: Boolean) {
|
||||||
|
LoadingIndicator.start()
|
||||||
|
lastPrefs?.let { currentPrefs ->
|
||||||
|
val newPrefs: Ipn.MaskedPrefs
|
||||||
|
if (isOn) {
|
||||||
|
newPrefs = setZeroRoutes(currentPrefs)
|
||||||
|
} else {
|
||||||
|
newPrefs = removeAllZeroRoutes(currentPrefs)
|
||||||
|
}
|
||||||
|
Client(viewModelScope).editPrefs(newPrefs) { result ->
|
||||||
|
LoadingIndicator.stop()
|
||||||
|
Log.d("RunExitNodeViewModel", "Edited prefs: $result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setZeroRoutes(prefs: Ipn.Prefs): Ipn.MaskedPrefs {
|
||||||
|
val newRoutes = (removeAllZeroRoutes(prefs).AdvertiseRoutes ?: emptyList()).toMutableList()
|
||||||
|
newRoutes.add("0.0.0.0/0")
|
||||||
|
newRoutes.add("::/0")
|
||||||
|
val newPrefs = Ipn.MaskedPrefs()
|
||||||
|
newPrefs.AdvertiseRoutes = newRoutes
|
||||||
|
return newPrefs
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeAllZeroRoutes(prefs: Ipn.Prefs): Ipn.MaskedPrefs {
|
||||||
|
val newRoutes = emptyList<String>().toMutableList()
|
||||||
|
(prefs.AdvertiseRoutes ?: emptyList()).forEach {
|
||||||
|
if (it != "0.0.0.0/0" && it != "::/0") {
|
||||||
|
newRoutes.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val newPrefs = Ipn.MaskedPrefs()
|
||||||
|
newPrefs.AdvertiseRoutes = newRoutes
|
||||||
|
return newPrefs
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17.6,9.48l1.84,-3.18c0.16,-0.31 0.04,-0.69 -0.26,-0.85c-0.29,-0.15 -0.65,-0.06 -0.83,0.22l-1.88,3.24c-2.86,-1.21 -6.08,-1.21 -8.94,0L5.65,5.67c-0.19,-0.29 -0.58,-0.38 -0.87,-0.2C4.5,5.65 4.41,6.01 4.56,6.3L6.4,9.48C3.3,11.25 1.28,14.44 1,18h22C22.72,14.44 20.7,11.25 17.6,9.48zM7,15.25c-0.69,0 -1.25,-0.56 -1.25,-1.25c0,-0.69 0.56,-1.25 1.25,-1.25S8.25,13.31 8.25,14C8.25,14.69 7.69,15.25 7,15.25zM17,15.25c-0.69,0 -1.25,-0.56 -1.25,-1.25c0,-0.69 0.56,-1.25 1.25,-1.25s1.25,0.56 1.25,1.25C18.25,14.69 17.69,15.25 17,15.25z"/>
|
||||||
|
|
||||||
|
</vector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="960" android:viewportWidth="960" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M40,840L40,760L920,760L920,840L40,840ZM160,720Q127,720 103.5,696.5Q80,673 80,640L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,640Q880,673 856.5,696.5Q833,720 800,720L160,720ZM160,640L800,640Q800,640 800,640Q800,640 800,640L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640ZM160,640Q160,640 160,640Q160,640 160,640L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,640Q160,640 160,640Q160,640 160,640L160,640Z"/>
|
||||||
|
|
||||||
|
</vector>
|
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-0.61 0.08,-1.21 0.21,-1.78L8.99,15v1c0,1.1 0.9,2 2,2v1.93C7.06,19.43 4,16.07 4,12zM17.89,17.4c-0.26,-0.81 -1,-1.4 -1.9,-1.4h-1v-3c0,-0.55 -0.45,-1 -1,-1h-6v-2h2c0.55,0 1,-0.45 1,-1L10.99,7h2c1.1,0 2,-0.9 2,-2v-0.41C17.92,5.77 20,8.65 20,12c0,2.08 -0.81,3.98 -2.11,5.4z"/>
|
||||||
|
|
||||||
|
</vector>
|
Loading…
Reference in New Issue