refactor: session/auth в MainViewModel (Этап 2 п.10, ч.2)
Верхнеуровневый session-жизненный цикл вынесен из AppScaffold в MainViewModel
(AndroidViewModel): держит session (StateFlow), login()=save+set,
logout()=ревокация app password + OfficeWarmup.clear + WebViewPool.dispose +
AuthStore.clear. AppScaffold читает vm.session.collectAsState(), forceLogout=
{vm.logout()}. Бонус: сессия переживает пересоздание Activity (поворот) —
раньше жила в remember и перечитывалась из AuthStore. UI-тумблеры (menuOpen,
sidebar/settings-флаги, pending* deep-link) осознанно оставлены в composable —
это чистое UI-состояние, не бизнес-логика.
This commit is contained in:
@@ -50,12 +50,10 @@ import kotlinx.coroutines.withContext
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.tasks.await
|
import kotlinx.coroutines.tasks.await
|
||||||
import ru.forbion.f7cloud.mobile.BuildConfig
|
|
||||||
import ru.forbion.f7cloud.core.auth.AppLockStore
|
import ru.forbion.f7cloud.core.auth.AppLockStore
|
||||||
import ru.forbion.f7cloud.core.auth.AppPasswordRevoker
|
|
||||||
import ru.forbion.f7cloud.core.auth.AuthSession
|
import ru.forbion.f7cloud.core.auth.AuthSession
|
||||||
import ru.forbion.f7cloud.core.auth.AuthStore
|
import androidx.compose.runtime.collectAsState
|
||||||
import ru.forbion.f7cloud.core.auth.AuthVerifier
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import ru.forbion.f7cloud.core.network.LoginFlowClient
|
import ru.forbion.f7cloud.core.network.LoginFlowClient
|
||||||
import ru.forbion.f7cloud.core.push.F7PushEvent
|
import ru.forbion.f7cloud.core.push.F7PushEvent
|
||||||
import ru.forbion.f7cloud.core.push.F7PushEventHub
|
import ru.forbion.f7cloud.core.push.F7PushEventHub
|
||||||
@@ -68,12 +66,10 @@ import ru.forbion.f7cloud.core.designsystem.F7BottomBarActions
|
|||||||
import ru.forbion.f7cloud.core.designsystem.F7BottomBarConfig
|
import ru.forbion.f7cloud.core.designsystem.F7BottomBarConfig
|
||||||
import ru.forbion.f7cloud.core.designsystem.F7MobileBottomBar
|
import ru.forbion.f7cloud.core.designsystem.F7MobileBottomBar
|
||||||
import ru.forbion.f7cloud.core.designsystem.F7Colors
|
import ru.forbion.f7cloud.core.designsystem.F7Colors
|
||||||
import ru.forbion.f7cloud.core.designsystem.F7TextButton
|
|
||||||
import ru.forbion.f7cloud.core.designsystem.F7Theme
|
import ru.forbion.f7cloud.core.designsystem.F7Theme
|
||||||
import ru.forbion.f7cloud.core.designsystem.f7SafeTopInsets
|
import ru.forbion.f7cloud.core.designsystem.f7SafeTopInsets
|
||||||
import ru.forbion.f7cloud.mobile.OfficeEditorActivity
|
import ru.forbion.f7cloud.mobile.OfficeEditorActivity
|
||||||
import ru.forbion.f7cloud.mobile.qr.F7QrScannerActivity
|
import ru.forbion.f7cloud.mobile.qr.F7QrScannerActivity
|
||||||
import ru.forbion.f7cloud.mobile.OfficeWebViewPool
|
|
||||||
import ru.forbion.f7cloud.feature.files.OfficeWarmup
|
import ru.forbion.f7cloud.feature.files.OfficeWarmup
|
||||||
import ru.forbion.f7cloud.mobile.permissions.F7AppPermissions
|
import ru.forbion.f7cloud.mobile.permissions.F7AppPermissions
|
||||||
import ru.forbion.f7cloud.mobile.permissions.F7PermissionRationaleDialog
|
import ru.forbion.f7cloud.mobile.permissions.F7PermissionRationaleDialog
|
||||||
@@ -95,8 +91,8 @@ fun AppScaffold(
|
|||||||
onRequestRuntimePermissions: (onFinished: (() -> Unit)?) -> Unit = {},
|
onRequestRuntimePermissions: (onFinished: (() -> Unit)?) -> Unit = {},
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val authStore = remember { AuthStore(context) }
|
val mainVm: MainViewModel = viewModel()
|
||||||
var session by remember { mutableStateOf(authStore.load()) }
|
val session by mainVm.session.collectAsState()
|
||||||
var activeTab by rememberSaveable(
|
var activeTab by rememberSaveable(
|
||||||
saver = Saver(
|
saver = Saver(
|
||||||
save = { state -> state.value.name },
|
save = { state -> state.value.name },
|
||||||
@@ -151,17 +147,7 @@ fun AppScaffold(
|
|||||||
context.applicationContext.getSharedPreferences("f7_permissions", android.content.Context.MODE_PRIVATE)
|
context.applicationContext.getSharedPreferences("f7_permissions", android.content.Context.MODE_PRIVATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
val logoutScope = rememberCoroutineScope()
|
val forceLogout = { mainVm.logout() }
|
||||||
val forceLogout = {
|
|
||||||
// Best-effort ревокация app password на сервере ДО очистки локальной сессии.
|
|
||||||
session?.let { current ->
|
|
||||||
logoutScope.launch { AppPasswordRevoker.revoke(current) }
|
|
||||||
}
|
|
||||||
OfficeWarmup.clear()
|
|
||||||
OfficeWebViewPool.dispose()
|
|
||||||
authStore.clear()
|
|
||||||
session = null
|
|
||||||
}
|
|
||||||
LaunchedEffect(session?.serverUrl, session?.username) {
|
LaunchedEffect(session?.serverUrl, session?.username) {
|
||||||
session?.let { OfficeWarmup.warm(it) }
|
session?.let { OfficeWarmup.warm(it) }
|
||||||
}
|
}
|
||||||
@@ -174,10 +160,7 @@ fun AppScaffold(
|
|||||||
|
|
||||||
F7Theme {
|
F7Theme {
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
LoginScreen(onLogin = {
|
LoginScreen(onLogin = { mainVm.login(it) })
|
||||||
authStore.save(it)
|
|
||||||
session = it
|
|
||||||
})
|
|
||||||
return@F7Theme
|
return@F7Theme
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package ru.forbion.f7cloud.mobile.ui
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.forbion.f7cloud.core.auth.AppPasswordRevoker
|
||||||
|
import ru.forbion.f7cloud.core.auth.AuthSession
|
||||||
|
import ru.forbion.f7cloud.core.auth.AuthStore
|
||||||
|
import ru.forbion.f7cloud.feature.files.OfficeWarmup
|
||||||
|
import ru.forbion.f7cloud.mobile.OfficeWebViewPool
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Держит session/auth-жизненный цикл верхнего уровня (вынесено из AppScaffold).
|
||||||
|
* В ViewModel сессия переживает пересоздание Activity (поворот и т.п.) — раньше
|
||||||
|
* жила в remember и перечитывалась из AuthStore.
|
||||||
|
*/
|
||||||
|
class MainViewModel(app: Application) : AndroidViewModel(app) {
|
||||||
|
private val authStore = AuthStore(app)
|
||||||
|
private val _session = MutableStateFlow(authStore.load())
|
||||||
|
val session: StateFlow<AuthSession?> = _session.asStateFlow()
|
||||||
|
|
||||||
|
fun login(newSession: AuthSession) {
|
||||||
|
authStore.save(newSession)
|
||||||
|
_session.value = newSession
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout() {
|
||||||
|
// Best-effort ревокация app password на сервере ДО очистки локальной сессии.
|
||||||
|
_session.value?.let { current ->
|
||||||
|
viewModelScope.launch { runCatching { AppPasswordRevoker.revoke(current) } }
|
||||||
|
}
|
||||||
|
OfficeWarmup.clear()
|
||||||
|
OfficeWebViewPool.dispose()
|
||||||
|
authStore.clear()
|
||||||
|
_session.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user