From 47534ceb59f761426400950cdc4e9a6e00fc1ef9 Mon Sep 17 00:00:00 2001 From: b-dev-mobile Date: Thu, 9 Jul 2026 15:02:21 +0000 Subject: [PATCH] =?UTF-8?q?refactor:=20session/auth=20=D0=B2=20MainViewMod?= =?UTF-8?q?el=20(=D0=AD=D1=82=D0=B0=D0=BF=202=20=D0=BF.10,=20=D1=87.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Верхнеуровневый 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-состояние, не бизнес-логика. --- .../forbion/f7cloud/mobile/ui/AppScaffold.kt | 29 +++---------- .../f7cloud/mobile/ui/MainViewModel.kt | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/ru/forbion/f7cloud/mobile/ui/MainViewModel.kt diff --git a/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppScaffold.kt b/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppScaffold.kt index 64589e7..7d07678 100644 --- a/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppScaffold.kt +++ b/app/src/main/java/ru/forbion/f7cloud/mobile/ui/AppScaffold.kt @@ -50,12 +50,10 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await -import ru.forbion.f7cloud.mobile.BuildConfig 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.AuthStore -import ru.forbion.f7cloud.core.auth.AuthVerifier +import androidx.compose.runtime.collectAsState +import androidx.lifecycle.viewmodel.compose.viewModel import ru.forbion.f7cloud.core.network.LoginFlowClient import ru.forbion.f7cloud.core.push.F7PushEvent 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.F7MobileBottomBar 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.f7SafeTopInsets import ru.forbion.f7cloud.mobile.OfficeEditorActivity import ru.forbion.f7cloud.mobile.qr.F7QrScannerActivity -import ru.forbion.f7cloud.mobile.OfficeWebViewPool import ru.forbion.f7cloud.feature.files.OfficeWarmup import ru.forbion.f7cloud.mobile.permissions.F7AppPermissions import ru.forbion.f7cloud.mobile.permissions.F7PermissionRationaleDialog @@ -95,8 +91,8 @@ fun AppScaffold( onRequestRuntimePermissions: (onFinished: (() -> Unit)?) -> Unit = {}, ) { val context = LocalContext.current - val authStore = remember { AuthStore(context) } - var session by remember { mutableStateOf(authStore.load()) } + val mainVm: MainViewModel = viewModel() + val session by mainVm.session.collectAsState() var activeTab by rememberSaveable( saver = Saver( save = { state -> state.value.name }, @@ -151,17 +147,7 @@ fun AppScaffold( context.applicationContext.getSharedPreferences("f7_permissions", android.content.Context.MODE_PRIVATE) } - val logoutScope = rememberCoroutineScope() - val forceLogout = { - // Best-effort ревокация app password на сервере ДО очистки локальной сессии. - session?.let { current -> - logoutScope.launch { AppPasswordRevoker.revoke(current) } - } - OfficeWarmup.clear() - OfficeWebViewPool.dispose() - authStore.clear() - session = null - } + val forceLogout = { mainVm.logout() } LaunchedEffect(session?.serverUrl, session?.username) { session?.let { OfficeWarmup.warm(it) } } @@ -174,10 +160,7 @@ fun AppScaffold( F7Theme { if (session == null) { - LoginScreen(onLogin = { - authStore.save(it) - session = it - }) + LoginScreen(onLogin = { mainVm.login(it) }) return@F7Theme } diff --git a/app/src/main/java/ru/forbion/f7cloud/mobile/ui/MainViewModel.kt b/app/src/main/java/ru/forbion/f7cloud/mobile/ui/MainViewModel.kt new file mode 100644 index 0000000..2aa2208 --- /dev/null +++ b/app/src/main/java/ru/forbion/f7cloud/mobile/ui/MainViewModel.kt @@ -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 = _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 + } +}