// 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 kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class IpnModel( notifier: Notifier, private val apiClient: LocalApiClient, scope: CoroutineScope ) { private var notifierSessions: MutableList = mutableListOf() private val _state: MutableStateFlow = MutableStateFlow(null) private val _netmap: MutableStateFlow = MutableStateFlow(null) private val _prefs: MutableStateFlow = MutableStateFlow(null) private val _engineStatus: MutableStateFlow = MutableStateFlow(null) private val _tailFSShares: MutableStateFlow?> = MutableStateFlow(null) private val _browseToURL: MutableStateFlow = MutableStateFlow(null) private val _loginFinished: MutableStateFlow = MutableStateFlow(null) private val _version: MutableStateFlow = MutableStateFlow(null) private val _loggedInUser: MutableStateFlow = MutableStateFlow(null) private val _loginProfiles: MutableStateFlow?> = MutableStateFlow(null) val state: StateFlow = _state val netmap: StateFlow = _netmap val prefs: StateFlow = _prefs val engineStatus: StateFlow = _engineStatus val tailFSShares: StateFlow?> = _tailFSShares val browseToURL: StateFlow = _browseToURL val loginFinished: StateFlow = _loginFinished val version: StateFlow = _version val loggedInUser: StateFlow = _loggedInUser val loginProfiles: StateFlow?> = _loginProfiles val isUsingExitNode: Boolean get() { return prefs.value != null } // Backend Observation private suspend fun loadUserProfiles() { LocalApiClient.isReady.first { it } apiClient.getProfiles { result -> result.success?.let { users -> _loginProfiles.value = users } ?: run { Log.e("IpnManager", "Error loading profiles: ${result.error}") } } apiClient.getCurrentProfile { result -> result.success?.let { user -> _loggedInUser.value = user } ?: run { Log.e("IpnManager", "Error loading profiles: ${result.error}") } } } private fun onNotifyChange(notify: Ipn.Notify) { notify.State?.let { state -> _state.value = Ipn.State.fromInt(state) } notify.NetMap?.let { netmap -> _netmap.value = netmap } notify.Prefs?.let { prefs -> _prefs.value = prefs } notify.Engine?.let { engine -> _engineStatus.value = engine } notify.TailFSShares?.let { shares -> _tailFSShares.value = shares } notify.BrowseToURL?.let { url -> _browseToURL.value = url } notify.LoginFinished?.let { message -> _loginFinished.value = message.property } notify.Version?.let { version -> _version.value = version } } init { Log.d("IpnModel", "IpnModel created") val session = notifier.watchAll { n -> onNotifyChange(n) } notifierSessions.add(session) scope.launch { loadUserProfiles() } } }