android/ui: cleanup unused code

Updates tailscale/corp#18202

Removes unused code from various legacy ported classes.  Fixes a number of soft warnings.

Removes/renames a few composables we were no longer using.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
jonathan/cleanup
Jonathan Nobels 1 month ago committed by Jonathan Nobels
parent b4c0a6931d
commit 59ddead48e

@ -1,39 +1,25 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn
import android.Manifest
import android.app.Activity
import android.app.Application
import android.app.DownloadManager
import android.app.Fragment
import android.app.FragmentTransaction
import android.app.NotificationChannel
import android.app.PendingIntent
import android.app.UiModeManager
import android.content.ContentResolver
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.net.Uri
import android.net.VpnService
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.provider.Settings
import android.util.Log
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.ActivityCompat.startActivityForResult
import androidx.core.app.NotificationManagerCompat
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
@ -62,22 +48,17 @@ class App : Application(), libtailscale.AppContext {
const val STATUS_NOTIFICATION_ID = 1
const val NOTIFY_CHANNEL_ID = "tailscale-notify"
const val NOTIFY_NOTIFICATION_ID = 2
private const val PEER_TAG = "peer"
private const val FILE_CHANNEL_ID = "tailscale-files"
private const val FILE_NOTIFICATION_ID = 3
private const val TAG = "App"
private val networkConnectivityRequest =
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build()
lateinit var appInstance: App
@JvmStatic
fun startActivityForResult(act: Activity, intent: Intent?, request: Int) {
val f: Fragment = act.fragmentManager.findFragmentByTag(PEER_TAG)
f.startActivityForResult(intent, request)
}
lateinit var appInstance: App
@JvmStatic
fun getApplication(): App {
@ -88,6 +69,7 @@ class App : Application(), libtailscale.AppContext {
val dns = DnsConfig()
var autoConnect = false
var vpnReady = false
private lateinit var connectivityManager: ConnectivityManager
private lateinit var app: libtailscale.Application
@ -107,12 +89,13 @@ class App : Application(), libtailscale.AppContext {
// to the given folder. We will preferentially use <shared>/Downloads and fallback to
// an app local directory "Taildrop" if we cannot create that. This mode does not support
// user notifications for incoming files.
val directFileDir = this.prepareDownloadsFolder()
val directFileDir = this.prepareDownloadsFolder().absolutePath
app = Libtailscale.start(dataDir, directFileDir.absolutePath, this)
app = Libtailscale.start(dataDir, directFileDir, this)
Request.setApp(app)
Notifier.setApp(app)
Notifier.start(applicationScope)
connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
setAndRegisterNetworkCallbacks()
createNotificationChannel(
@ -121,6 +104,7 @@ class App : Application(), libtailscale.AppContext {
STATUS_CHANNEL_ID, "VPN Status", NotificationManagerCompat.IMPORTANCE_LOW)
createNotificationChannel(
FILE_CHANNEL_ID, "File transfers", NotificationManagerCompat.IMPORTANCE_DEFAULT)
appInstance = this
applicationScope.launch {
Notifier.tileReady.collect { isTileReady -> setTileReady(isTileReady) }
@ -219,9 +203,6 @@ class App : Application(), libtailscale.AppContext {
}
fun setTileReady(ready: Boolean) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return
}
QuickToggleService.setReady(this, ready)
Log.d("App", "Set Tile Ready: $ready $autoConnect")
vpnReady = ready
@ -231,9 +212,6 @@ class App : Application(), libtailscale.AppContext {
}
fun setTileStatus(status: Boolean) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
return
}
QuickToggleService.setStatus(this, status)
}
@ -265,18 +243,6 @@ class App : Application(), libtailscale.AppContext {
return null
}
// attachPeer adds a Peer fragment for tracking the Activity
// lifecycle.
fun attachPeer(act: Activity) {
act.runOnUiThread(
Runnable {
val ft: FragmentTransaction = act.fragmentManager.beginTransaction()
ft.add(Peer(), PEER_TAG)
ft.commit()
act.fragmentManager.executePendingTransactions()
})
}
override fun isChromeOS(): Boolean {
return packageManager.hasSystemFeature("android.hardware.type.pc")
}
@ -288,102 +254,12 @@ class App : Application(), libtailscale.AppContext {
if (intent == null) {
startVPN()
} else {
startActivityForResult(act, intent, reqCode)
startActivityForResult(act, intent, reqCode, null)
}
})
}
fun showURL(act: Activity, url: String?) {
act.runOnUiThread(
Runnable {
val builder: CustomTabsIntent.Builder = CustomTabsIntent.Builder()
val headerColor = -0xb69b6b
builder.setToolbarColor(headerColor)
val intent: CustomTabsIntent = builder.build()
intent.launchUrl(act, Uri.parse(url))
})
}
@get:Throws(Exception::class)
val packageCertificate: ByteArray?
// getPackageSignatureFingerprint returns the first package signing certificate, if any.
get() {
val info: PackageInfo
info = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
for (signature in info.signatures) {
return signature.toByteArray()
}
return null
}
@Throws(IOException::class)
fun insertMedia(name: String?, mimeType: String): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver: ContentResolver = contentResolver
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
if ("" != mimeType) {
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
}
val root: Uri = MediaStore.Files.getContentUri("external")
resolver.insert(root, contentValues).toString()
} else {
val dir: File = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
dir.mkdirs()
val f = File(dir, name)
Uri.fromFile(f).toString()
}
}
@Throws(IOException::class)
fun openUri(uri: String?, mode: String?): Int? {
val resolver: ContentResolver = contentResolver
return mode?.let { resolver.openFileDescriptor(Uri.parse(uri), it)?.detachFd() }
}
fun deleteUri(uri: String?) {
val resolver: ContentResolver = contentResolver
resolver.delete(Uri.parse(uri), null, null)
}
fun notifyFile(uri: String?, msg: String?) {
val viewIntent: Intent
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
viewIntent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
} else {
// uri is a file:// which is not allowed to be shared outside the app.
viewIntent = Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
}
val pending: PendingIntent =
PendingIntent.getActivity(this, 0, viewIntent, PendingIntent.FLAG_UPDATE_CURRENT)
val builder: NotificationCompat.Builder =
NotificationCompat.Builder(this, FILE_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle("File received")
.setContentText(msg)
.setContentIntent(pending)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
val nm: NotificationManagerCompat = NotificationManagerCompat.from(this)
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) !=
PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return
}
nm.notify(FILE_NOTIFICATION_ID, builder.build())
}
fun createNotificationChannel(id: String?, name: String?, importance: Int) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return
}
val channel = NotificationChannel(id, name, importance)
val nm: NotificationManagerCompat = NotificationManagerCompat.from(this)
nm.createNotificationChannel(channel)
@ -424,12 +300,7 @@ class App : Application(), libtailscale.AppContext {
return sb.toString()
}
fun isTV(): Boolean {
val mm = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
return mm.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
}
fun prepareDownloadsFolder(): File {
private fun prepareDownloadsFolder(): File {
var downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
try {

@ -0,0 +1,23 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package com.tailscale.ipn
// The package identifiers for applications which are known to break when Tailscale is enabled.
object DisallowedApps {
val apps =
arrayOf(
// RCS/Jibe https://github.com/tailscale/tailscale/issues/2322
"com.google.android.apps.messaging",
// Stadia https://github.com/tailscale/tailscale/issues/3460
"com.google.stadia.android",
// Android Auto https://github.com/tailscale/tailscale/issues/3828
"com.google.android.projection.gearhead",
// GoPro https://github.com/tailscale/tailscale/issues/2554
"com.gopro.smarty",
// Sonos https://github.com/tailscale/tailscale/issues/2548
"com.sonos.acr",
"com.sonos.acr2",
// Google Chromecast https://github.com/tailscale/tailscale/issues/3636
"com.google.android.apps.chromecast.app")
}

@ -44,7 +44,7 @@ open class IPNService : VpnService(), libtailscale.IPNService {
return START_STICKY
}
override public fun close() {
override fun close() {
stopForeground(true)
Libtailscale.serviceDisconnect(this)
val app = applicationContext as App
@ -72,7 +72,7 @@ open class IPNService : VpnService(), libtailscale.IPNService {
private fun disallowApp(b: Builder, name: String) {
try {
b.addDisallowedApplication(name)
} catch (e: PackageManager.NameNotFoundException) {}
} catch (_: PackageManager.NameNotFoundException) {}
}
override fun newBuilder(): VPNServiceBuilder {
@ -83,27 +83,10 @@ open class IPNService : VpnService(), libtailscale.IPNService {
.allowFamily(OsConstants.AF_INET6)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
b.setMetered(false) // Inherit the metered status from the underlying networks.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
b.setUnderlyingNetworks(null) // Use all available networks.
b.setUnderlyingNetworks(null) // Use all available networks.
// RCS/Jibe https://github.com/tailscale/tailscale/issues/2322
disallowApp(b, "com.google.android.apps.messaging")
DisallowedApps.apps.forEach { disallowApp(b, it) }
// Stadia https://github.com/tailscale/tailscale/issues/3460
disallowApp(b, "com.google.stadia.android")
// Android Auto https://github.com/tailscale/tailscale/issues/3828
disallowApp(b, "com.google.android.projection.gearhead")
// GoPro https://github.com/tailscale/tailscale/issues/2554
disallowApp(b, "com.gopro.smarty")
// Sonos https://github.com/tailscale/tailscale/issues/2548
disallowApp(b, "com.sonos.acr")
disallowApp(b, "com.sonos.acr2")
// Google Chromecast https://github.com/tailscale/tailscale/issues/3636
disallowApp(b, "com.google.android.apps.chromecast.app")
return VPNServiceBuilder(b)
}

@ -51,16 +51,15 @@ class ShareActivity : ComponentActivity() {
}
// Loads the files from the intent.
fun loadFiles() {
private fun loadFiles() {
if (intent == null) {
Log.e(TAG, "Share failure - No intent found")
return
}
val act = intent.action
val uris: List<Uri?>?
uris =
val uris: List<Uri?>? =
when (act) {
Intent.ACTION_SEND -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {

@ -11,6 +11,7 @@ import android.content.Intent;
import android.net.VpnService;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
@ -22,6 +23,7 @@ public final class StartVPNWorker extends Worker {
super(appContext, workerParams);
}
@NonNull
@Override
public Result doWork() {
App app = ((App) getApplicationContext());

@ -6,26 +6,20 @@ package com.tailscale.ipn.ui.view
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.tailscale.ipn.ui.theme.link
// Action button that fills the full width of it's container with 12dp of vertical padding
@Composable
fun PrimaryActionButton(onClick: () -> Unit, content: @Composable RowScope.() -> Unit) {
fun FullWidthButton(onClick: () -> Unit, content: @Composable RowScope.() -> Unit) {
Button(
onClick = onClick,
contentPadding = PaddingValues(vertical = 12.dp),
@ -33,6 +27,7 @@ fun PrimaryActionButton(onClick: () -> Unit, content: @Composable RowScope.() ->
content = content)
}
// Hyperlink style text button
@Composable
fun OpenURLButton(title: String, url: String) {
val handler = LocalUriHandler.current
@ -46,19 +41,3 @@ fun OpenURLButton(title: String, url: String) {
)
}
}
@Composable
fun ClearButton(onClick: () -> Unit) {
IconButton(onClick = onClick, modifier = Modifier.size(24.dp)) {
Icon(Icons.Outlined.Clear, null)
}
}
@Composable
fun CloseButton() {
val focusManager = LocalFocusManager.current
IconButton(onClick = { focusManager.clearFocus() }, modifier = Modifier.size(24.dp)) {
Icon(Icons.Outlined.Close, null)
}
}

@ -10,7 +10,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.tailscale.ipn.R
// Defines the different types of error dialogs.
// Provides a message and title for each error type.
enum class ErrorDialogType {
INVALID_CUSTOM_URL,
LOGOUT_FAILED,
@ -64,6 +65,6 @@ fun ErrorDialog(
title = { Text(text = stringResource(id = title)) },
text = { Text(text = stringResource(id = message)) },
confirmButton = {
PrimaryActionButton(onClick = onDismiss) { Text(text = stringResource(id = buttonText)) }
FullWidthButton(onClick = onDismiss) { Text(text = stringResource(id = buttonText)) }
})
}

@ -22,7 +22,6 @@ import com.tailscale.ipn.mdm.MDMSettings
import com.tailscale.ipn.ui.util.itemsWithDividers
import com.tailscale.ipn.ui.viewModel.IpnViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MDMSettingsDebugView(nav: BackNavigation, model: IpnViewModel = viewModel()) {
Scaffold(topBar = { Header(R.string.current_mdm_settings, onBack = nav.onBack) }) { innerPadding

@ -28,7 +28,6 @@ import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
@ -276,7 +275,7 @@ fun ConnectView(
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.size(1.dp))
PrimaryActionButton(onClick = connectAction) {
FullWidthButton(onClick = connectAction) {
Text(
text = stringResource(id = R.string.connect),
fontSize = MaterialTheme.typography.titleMedium.fontSize)
@ -293,7 +292,7 @@ fun ConnectView(
style = MaterialTheme.typography.titleSmall,
textAlign = TextAlign.Center)
Spacer(modifier = Modifier.size(1.dp))
PrimaryActionButton(onClick = loginAction) {
FullWidthButton(onClick = loginAction) {
Text(
text = stringResource(id = R.string.log_in),
fontSize = MaterialTheme.typography.titleMedium.fontSize)
@ -304,7 +303,7 @@ fun ConnectView(
}
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PeerList(
viewModel: MainViewModel,

@ -22,7 +22,6 @@ import com.tailscale.ipn.ui.viewModel.ExitNodePickerNav
import com.tailscale.ipn.ui.viewModel.ExitNodePickerViewModel
import com.tailscale.ipn.ui.viewModel.ExitNodePickerViewModelFactory
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MullvadExitNodePicker(
countryCode: String,

@ -40,7 +40,6 @@ import com.tailscale.ipn.ui.util.itemsWithDividers
import com.tailscale.ipn.ui.viewModel.PeerDetailsViewModel
import com.tailscale.ipn.ui.viewModel.PeerDetailsViewModelFactory
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PeerDetails(
nav: BackNavigation,

@ -23,7 +23,6 @@ import com.tailscale.ipn.ui.model.Permissions
import com.tailscale.ipn.ui.theme.success
import com.tailscale.ipn.ui.util.itemsWithDividers
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionsView(nav: BackNavigation, openApplicationSettings: () -> Unit) {
val permissions = Permissions.withGrantedStatus

@ -119,7 +119,7 @@ fun FileShareConnectView(onToggle: () -> Unit) {
stringResource(R.string.connect_to_your_tailnet_to_share_files),
style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.size(1.dp))
PrimaryActionButton(onClick = onToggle) {
FullWidthButton(onClick = onToggle) {
Text(
text = stringResource(id = R.string.connect),
fontSize = MaterialTheme.typography.titleMedium.fontSize)

@ -20,14 +20,14 @@ import kotlinx.coroutines.flow.StateFlow
import kotlin.concurrent.timer
// DotsMatrix represents the state of the progress indicator.
typealias DotsMatrix = List<List<Boolean>>
typealias DotsMatrix = Array<Array<Boolean>>
// The initial DotsMatrix that represents the Tailscale logo (T-shaped).
val logoDotsMatrix: DotsMatrix =
listOf(
listOf(false, false, false),
listOf(true, true, true),
listOf(false, true, false),
arrayOf(
arrayOf(false, false, false),
arrayOf(true, true, true),
arrayOf(false, true, false),
)
@Composable
@ -81,56 +81,56 @@ fun TailscaleLogoView(animated: Boolean = false, modifier: Modifier) {
}
}
val gameOfLife: List<DotsMatrix> =
listOf(
listOf(
listOf(false, true, true),
listOf(true, false, true),
listOf(false, false, true),
val gameOfLife: Array<DotsMatrix> =
arrayOf(
arrayOf(
arrayOf(false, true, true),
arrayOf(true, false, true),
arrayOf(false, false, true),
),
listOf(
listOf(false, true, true),
listOf(false, false, true),
listOf(false, true, false),
arrayOf(
arrayOf(false, true, true),
arrayOf(false, false, true),
arrayOf(false, true, false),
),
listOf(
listOf(false, true, true),
listOf(false, false, false),
listOf(false, false, true),
arrayOf(
arrayOf(false, true, true),
arrayOf(false, false, false),
arrayOf(false, false, true),
),
listOf(
listOf(false, false, true),
listOf(false, true, false),
listOf(false, false, false),
arrayOf(
arrayOf(false, false, true),
arrayOf(false, true, false),
arrayOf(false, false, false),
),
listOf(
listOf(false, true, false),
listOf(false, false, false),
listOf(false, false, false),
arrayOf(
arrayOf(false, true, false),
arrayOf(false, false, false),
arrayOf(false, false, false),
),
listOf(
listOf(false, false, false),
listOf(false, false, true),
listOf(false, false, false),
arrayOf(
arrayOf(false, false, false),
arrayOf(false, false, true),
arrayOf(false, false, false),
),
listOf(
listOf(false, false, false),
listOf(false, false, false),
listOf(false, false, false),
arrayOf(
arrayOf(false, false, true),
arrayOf(false, false, false),
arrayOf(false, false, false),
),
listOf(
listOf(false, false, true),
listOf(false, false, false),
listOf(false, false, false),
arrayOf(
arrayOf(false, false, false),
arrayOf(false, false, false),
arrayOf(true, false, false),
),
listOf(
listOf(false, false, false),
listOf(false, false, false),
listOf(true, false, false),
),
listOf(listOf(false, false, false), listOf(false, false, false), listOf(true, true, false)),
listOf(listOf(false, false, false), listOf(true, false, false), listOf(true, true, false)),
listOf(listOf(false, false, false), listOf(true, true, false), listOf(false, true, false)),
listOf(listOf(false, false, false), listOf(true, true, false), listOf(false, true, true)),
listOf(listOf(false, false, false), listOf(true, true, true), listOf(false, false, true)),
listOf(listOf(false, true, false), listOf(true, true, true), listOf(true, false, true)))
arrayOf(
arrayOf(false, false, false), arrayOf(false, false, false), arrayOf(true, true, false)),
arrayOf(
arrayOf(false, false, false), arrayOf(true, false, false), arrayOf(true, true, false)),
arrayOf(
arrayOf(false, false, false), arrayOf(true, true, false), arrayOf(false, true, false)),
arrayOf(
arrayOf(false, false, false), arrayOf(true, true, false), arrayOf(false, true, true)),
arrayOf(
arrayOf(false, false, false), arrayOf(true, true, true), arrayOf(false, false, true)),
arrayOf(arrayOf(false, true, false), arrayOf(true, true, true), arrayOf(true, false, true)))

@ -77,11 +77,8 @@ fun UserSwitcherView(
}
// When switch is invoked, this stores the ID of the user we're trying to switch to
// so we can decorate it with a spinner. The actual logged in user will not change
// until
// we get our first netmap update back with the new userId for SelfNode.
// (jonathan) TODO: This user switch is not immediate. We may need to represent the
// "switching users" state globally (if ipnState is insufficient)
// so we can decorate it with a spinner. The actual logged in user will get updated
// as soon as we switch states.
val nextUserId = remember { mutableStateOf<String?>(null) }
LazyColumn {
@ -165,7 +162,7 @@ fun FusMenu(viewModel: UserSwitcherViewModel) {
Spacer(modifier = Modifier.padding(8.dp))
PrimaryActionButton(onClick = { viewModel.setControlURL(url) }) {
FullWidthButton(onClick = { viewModel.setControlURL(url) }) {
Text(stringResource(id = R.string.add_account_short))
}
}

@ -3,9 +3,6 @@
package com.tailscale.ipn.ui.viewModel
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.util.Log
import androidx.lifecycle.ViewModel
@ -60,13 +57,6 @@ open class IpnViewModel : ViewModel() {
Log.d(TAG, "Created")
}
protected fun Context.findActivity(): Activity? =
when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
private fun loadUserProfiles() {
Client(viewModelScope).profiles { result ->
result.onSuccess(loginProfiles::set).onFailure {
@ -136,59 +126,4 @@ open class IpnViewModel : ViewModel() {
completionHandler(it)
}
}
fun deleteProfile(profile: IpnLocal.LoginProfile, completionHandler: (Result<String>) -> Unit) {
Client(viewModelScope).deleteProfile(profile) {
viewModelScope.launch { loadUserProfiles() }
completionHandler(it)
}
}
// The below handle all types of preference modifications typically invoked by the UI.
// Callers generally shouldn't care about the returned prefs value - the source of
// truth is the IPNModel, who's prefs flow will change in value to reflect the true
// value of the pref setting in the back end (and will match the value returned here).
// Generally, you will want to inspect the returned value in the callback for errors
// to indicate why a particular setting did not change in the interface.
//
// Usage:
// - User/Interface changed to new value. Render the new value.
// - Submit the new value to the PrefsEditor
// - Observe the prefs on the IpnModel and update the UI when/if the value changes.
// For a typical flow, the changed value should reflect the value already shown.
// - Inform the user of any error which may have occurred
//
// The "toggle' functions here will attempt to set the pref value to the inverse of
// what is currently known in the IpnModel.prefs. If IpnModel.prefs is not available,
// the callback will be called with a NO_PREFS error
fun setWantRunning(wantRunning: Boolean, callback: (Result<Ipn.Prefs>) -> Unit) {
Ipn.MaskedPrefs().WantRunning = wantRunning
Client(viewModelScope).editPrefs(Ipn.MaskedPrefs(), callback)
}
fun toggleShieldsUp(callback: (Result<Ipn.Prefs>) -> Unit) {
val prefs =
Notifier.prefs.value
?: run {
callback(Result.failure(Exception("no prefs")))
return@toggleShieldsUp
}
val prefsOut = Ipn.MaskedPrefs()
prefsOut.ShieldsUp = !prefs.ShieldsUp
Client(viewModelScope).editPrefs(prefsOut, callback)
}
fun toggleRouteAll(callback: (Result<Ipn.Prefs>) -> Unit) {
val prefs =
Notifier.prefs.value
?: run {
callback(Result.failure(Exception("no prefs")))
return@toggleRouteAll
}
val prefsOut = Ipn.MaskedPrefs()
prefsOut.RouteAll = !prefs.RouteAll
Client(viewModelScope).editPrefs(prefsOut, callback)
}
}

Loading…
Cancel
Save