// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause package com.tailscale.ipn.ui.service import android.util.Log import com.tailscale.ipn.ui.localapi.LocalApiClient import com.tailscale.ipn.ui.model.Ipn import com.tailscale.ipn.ui.model.IpnLocal import com.tailscale.ipn.ui.model.Netmap import com.tailscale.ipn.ui.notifier.Notifier import com.tailscale.ipn.ui.util.set import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch class IpnModel( notifier: Notifier, val apiClient: LocalApiClient, val scope: CoroutineScope ) { private var notifierSessions: MutableList = mutableListOf() val state: StateFlow = MutableStateFlow(Ipn.State.NoState) val netmap: StateFlow = MutableStateFlow(null) val prefs: StateFlow = MutableStateFlow(null) val engineStatus: StateFlow = MutableStateFlow(null) val tailFSShares: StateFlow?> = MutableStateFlow(null) val browseToURL: StateFlow = MutableStateFlow(null) val loginFinished: StateFlow = MutableStateFlow(null) val version: StateFlow = MutableStateFlow(null) val loggedInUser: StateFlow = MutableStateFlow(null) val loginProfiles: StateFlow?> = MutableStateFlow(null) val isUsingExitNode: Boolean get() { return prefs.value != null } // Backend Observation private suspend fun loadUserProfiles() { LocalApiClient.isReady.await() apiClient.getProfiles { result -> result.success?.let(loginProfiles::set) ?: run { Log.e( "IpnManager", "Error loading profiles: ${result.error}" ) } } apiClient.getCurrentProfile { result -> result.success?.let(loggedInUser::set) ?: run { Log.e( "IpnManager", "Error loading current profile: ${result.error}" ) } } } private fun onNotifyChange(notify: Ipn.Notify) { notify.State?.let { s -> // Refresh the user profiles if we're transitioning out of the // NeedsLogin state. if (state.value == Ipn.State.NeedsLogin) { scope.launch { loadUserProfiles() } } Log.d("IpnModel", "State changed: $s") state.set(Ipn.State.fromInt(s)) } notify.NetMap?.let(netmap::set) notify.Prefs?.let(prefs::set) notify.Engine?.let(engineStatus::set) notify.TailFSShares?.let(tailFSShares::set) notify.BrowseToURL?.let(browseToURL::set) notify.LoginFinished?.let { loginFinished.set(it.property) } notify.Version?.let(version::set) } init { Log.d("IpnModel", "IpnModel created") val session = notifier.watchAll { n -> onNotifyChange(n) } notifierSessions.add(session) scope.launch { loadUserProfiles() } } }