mirror of
https://github.com/bitwarden/android.git
synced 2026-10-22 02:53:19 -05:00
Move the AppStateManager to the data module (#6593)
This commit is contained in:
1 parent
3538ca54ca
commit
8a2b46e81a
17 files changed
+112
-57
No files matched your search
@@ -5,13 +5,13 @@ import android.view.autofill.AutofillManager
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.bitwarden.data.manager.appstate.AppStateManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillActivityManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.manager.AutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
||||
+1
-1
@@ -2,10 +2,10 @@ package com.x8bit.bitwarden.data.autofill.manager
|
||||
|
||||
import android.view.autofill.AutofillManager
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.bitwarden.data.manager.appstate.AppStateManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
package com.x8bit.bitwarden.data.autofill.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.app.assist.AssistStructure
|
||||
import android.content.Context
|
||||
@@ -13,6 +12,10 @@ import android.view.autofill.AutofillManager
|
||||
import androidx.core.os.bundleOf
|
||||
import com.bitwarden.annotation.OmitFromCoverage
|
||||
import com.bitwarden.core.util.toPendingIntentMutabilityFlag
|
||||
import com.bitwarden.data.autofill.util.AUTOFILL_BUNDLE_KEY
|
||||
import com.bitwarden.data.autofill.util.AUTOFILL_CALLBACK_DATA_KEY
|
||||
import com.bitwarden.data.autofill.util.AUTOFILL_SAVE_ITEM_DATA_KEY
|
||||
import com.bitwarden.data.autofill.util.AUTOFILL_SELECTION_DATA_KEY
|
||||
import com.bitwarden.ui.platform.util.getSafeParcelableExtra
|
||||
import com.x8bit.bitwarden.AutofillCallbackActivity
|
||||
import com.x8bit.bitwarden.MainActivity
|
||||
@@ -22,11 +25,6 @@ import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||
import kotlin.random.Random
|
||||
|
||||
private const val AUTOFILL_SAVE_ITEM_DATA_KEY = "autofill-save-item-data"
|
||||
private const val AUTOFILL_SELECTION_DATA_KEY = "autofill-selection-data"
|
||||
private const val AUTOFILL_CALLBACK_DATA_KEY = "autofill-callback-data"
|
||||
private const val AUTOFILL_BUNDLE_KEY = "autofill-bundle-key"
|
||||
|
||||
/**
|
||||
* Creates an [Intent] in order to send the user to a manual selection process for autofill.
|
||||
*/
|
||||
@@ -149,12 +147,3 @@ fun Intent.getAutofillSelectionDataOrNull(): AutofillSelectionData? =
|
||||
fun Intent.getAutofillCallbackIntentOrNull(): AutofillCallbackData? =
|
||||
getBundleExtra(AUTOFILL_BUNDLE_KEY)
|
||||
?.getSafeParcelableExtra(AUTOFILL_CALLBACK_DATA_KEY)
|
||||
|
||||
/**
|
||||
* Checks if the given [Activity] was created for Autofill. This is useful to avoid locking the
|
||||
* vault if one of the Autofill services starts the only instance of the [MainActivity].
|
||||
*/
|
||||
val Activity.createdForAutofill: Boolean
|
||||
get() = intent.getAutofillSelectionDataOrNull() != null ||
|
||||
intent.getAutofillSaveItemOrNull() != null ||
|
||||
intent.getAutofillAssistStructureOrNull() != null
|
||||
-8
@@ -30,8 +30,6 @@ import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.legacy.LegacyAppCenterMigrator
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppResumeManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppResumeManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.AssetManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.AssetManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.BiometricsEncryptionManager
|
||||
@@ -106,12 +104,6 @@ import javax.inject.Singleton
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object PlatformManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppStateManager(
|
||||
application: Application,
|
||||
): AppStateManager = AppStateManagerImpl(application = application)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthenticatorBridgeProcessor(
|
||||
|
||||
@@ -17,6 +17,9 @@ import com.bitwarden.core.data.util.concurrentMapOf
|
||||
import com.bitwarden.core.data.util.flatMap
|
||||
import com.bitwarden.crypto.HashPurpose
|
||||
import com.bitwarden.crypto.Kdf
|
||||
import com.bitwarden.data.manager.appstate.AppStateManager
|
||||
import com.bitwarden.data.manager.appstate.model.AppCreationState
|
||||
import com.bitwarden.data.manager.appstate.model.AppForegroundState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
import com.x8bit.bitwarden.data.auth.manager.KdfManager
|
||||
@@ -29,9 +32,6 @@ import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.userAccountTokens
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.userSwitchingChangesFlow
|
||||
import com.x8bit.bitwarden.data.platform.error.NoActiveUserException
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Context
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
import com.bitwarden.core.data.manager.realtime.RealtimeManager
|
||||
import com.bitwarden.cxf.parser.CredentialExchangePayloadParser
|
||||
import com.bitwarden.data.manager.appstate.AppStateManager
|
||||
import com.bitwarden.data.manager.file.FileManager
|
||||
import com.bitwarden.network.service.CiphersService
|
||||
import com.bitwarden.network.service.FolderService
|
||||
@@ -16,7 +17,6 @@ import com.x8bit.bitwarden.data.auth.manager.TrustedDeviceManager
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
||||
import com.x8bit.bitwarden.data.auth.manager.UserStateManager
|
||||
import com.x8bit.bitwarden.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PolicyManager
|
||||
|
||||
+2
-2
@@ -3,13 +3,13 @@ package com.x8bit.bitwarden.data.autofill.manager
|
||||
import android.view.autofill.AutofillManager
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.data.manager.appstate.AppStateManager
|
||||
import com.bitwarden.data.manager.appstate.model.AppForegroundState
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManager
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillEnabledManagerImpl
|
||||
import com.x8bit.bitwarden.data.autofill.manager.browser.BrowserThirdPartyAutofillManager
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutoFillData
|
||||
import com.x8bit.bitwarden.data.autofill.model.browser.BrowserThirdPartyAutofillStatus
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
|
||||
@@ -14,6 +14,9 @@ import com.bitwarden.core.data.manager.realtime.RealtimeManager
|
||||
import com.bitwarden.core.data.util.asFailure
|
||||
import com.bitwarden.core.data.util.asSuccess
|
||||
import com.bitwarden.crypto.HashPurpose
|
||||
import com.bitwarden.data.manager.appstate.FakeAppStateManager
|
||||
import com.bitwarden.data.manager.appstate.model.AppCreationState
|
||||
import com.bitwarden.data.manager.appstate.model.AppForegroundState
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
@@ -26,9 +29,6 @@ import com.x8bit.bitwarden.data.auth.manager.model.LogoutEvent
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UpdateKdfMinimumsResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toSdkParams
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.FakeAppStateManager
|
||||
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
|
||||
|
||||
@@ -54,6 +54,7 @@ dependencies {
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.security.crypto)
|
||||
implementation(libs.androidx.lifecycle.process)
|
||||
implementation(libs.google.hilt.android)
|
||||
ksp(libs.google.hilt.compiler)
|
||||
implementation(libs.kotlinx.serialization)
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
@file:OmitFromCoverage
|
||||
|
||||
package com.bitwarden.data.autofill.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import com.bitwarden.annotation.OmitFromCoverage
|
||||
|
||||
/**
|
||||
* A unique key used to set or get Autofill saved item data.
|
||||
*/
|
||||
const val AUTOFILL_SAVE_ITEM_DATA_KEY = "autofill-save-item-data"
|
||||
|
||||
/**
|
||||
* A unique key used to set or get Autofill selection data.
|
||||
*/
|
||||
const val AUTOFILL_SELECTION_DATA_KEY = "autofill-selection-data"
|
||||
|
||||
/**
|
||||
* A unique key used to set or get Autofill callback data.
|
||||
*/
|
||||
const val AUTOFILL_CALLBACK_DATA_KEY = "autofill-callback-data"
|
||||
|
||||
/**
|
||||
* A unique key used to set and get the `Bundle` containing autofill data.
|
||||
*/
|
||||
const val AUTOFILL_BUNDLE_KEY = "autofill-bundle-key"
|
||||
|
||||
/**
|
||||
* Checks if the given [Intent] contains an `AutofillSaveItem` related to an ongoing save item
|
||||
* process.
|
||||
*/
|
||||
private val Intent.hasAutofillSaveItem: Boolean
|
||||
get() = this.hasExtra(AUTOFILL_SAVE_ITEM_DATA_KEY)
|
||||
|
||||
/**
|
||||
* Checks if the given [Intent] contains data about an ongoing manual autofill selection process.
|
||||
*/
|
||||
private val Intent.hasAutofillSelectionData: Boolean
|
||||
get() = getBundleExtra(AUTOFILL_BUNDLE_KEY)
|
||||
?.containsKey(AUTOFILL_SELECTION_DATA_KEY) == true
|
||||
|
||||
/**
|
||||
* Checks if the given [Intent] contains Autofill callback data.
|
||||
*/
|
||||
private val Intent.hasAutofillCallbackIntent: Boolean
|
||||
get() = getBundleExtra(AUTOFILL_BUNDLE_KEY)?.containsKey(AUTOFILL_CALLBACK_DATA_KEY) == true
|
||||
|
||||
/**
|
||||
* Checks if the given [Activity] was created for Autofill. This is useful to avoid locking the
|
||||
* vault if one of the Autofill services starts the only instance of the `Activity`.
|
||||
*/
|
||||
val Activity.createdForAutofill: Boolean
|
||||
get() = intent.hasAutofillSelectionData ||
|
||||
intent.hasAutofillSaveItem ||
|
||||
intent.hasAutofillCallbackIntent
|
||||
+5
-6
@@ -1,8 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
package com.bitwarden.data.manager.appstate
|
||||
|
||||
import com.x8bit.bitwarden.data.autofill.accessibility.BitwardenAccessibilityService
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
|
||||
import com.bitwarden.data.manager.appstate.model.AppCreationState
|
||||
import com.bitwarden.data.manager.appstate.model.AppForegroundState
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
/**
|
||||
@@ -12,8 +11,8 @@ interface AppStateManager {
|
||||
/**
|
||||
* Emits whenever there are changes to the app creation state.
|
||||
*
|
||||
* This is required because the [BitwardenAccessibilityService] will keep the app process alive
|
||||
* when the app would otherwise be destroyed.
|
||||
* This is required because the Accessibility Service will keep the app process alive when the
|
||||
* app would otherwise be destroyed.
|
||||
*/
|
||||
val appCreatedStateFlow: StateFlow<AppCreationState>
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
package com.bitwarden.data.manager.appstate
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
@@ -6,9 +6,9 @@ import android.os.Bundle
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.x8bit.bitwarden.data.autofill.util.createdForAutofill
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
|
||||
import com.bitwarden.data.autofill.util.createdForAutofill
|
||||
import com.bitwarden.data.manager.appstate.model.AppCreationState
|
||||
import com.bitwarden.data.manager.appstate.model.AppForegroundState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -17,7 +17,7 @@ import timber.log.Timber
|
||||
/**
|
||||
* Primary implementation of [AppStateManager].
|
||||
*/
|
||||
class AppStateManagerImpl(
|
||||
internal class AppStateManagerImpl(
|
||||
application: Application,
|
||||
processLifecycleOwner: LifecycleOwner = ProcessLifecycleOwner.get(),
|
||||
) : AppStateManager {
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
package com.bitwarden.data.manager.appstate.model
|
||||
|
||||
/**
|
||||
* Represents the creation state of the app.
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.model
|
||||
package com.bitwarden.data.manager.appstate.model
|
||||
|
||||
/**
|
||||
* Represents the foreground state of the app.
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.bitwarden.data.manager.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.bitwarden.core.data.manager.BuildInfoManager
|
||||
import com.bitwarden.core.data.manager.dispatcher.DispatcherManager
|
||||
@@ -8,6 +9,8 @@ import com.bitwarden.data.manager.BitwardenPackageManager
|
||||
import com.bitwarden.data.manager.BitwardenPackageManagerImpl
|
||||
import com.bitwarden.data.manager.NativeLibraryManager
|
||||
import com.bitwarden.data.manager.NativeLibraryManagerImpl
|
||||
import com.bitwarden.data.manager.appstate.AppStateManager
|
||||
import com.bitwarden.data.manager.appstate.AppStateManagerImpl
|
||||
import com.bitwarden.data.manager.file.FileManager
|
||||
import com.bitwarden.data.manager.file.FileManagerImpl
|
||||
import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager
|
||||
@@ -30,6 +33,12 @@ import javax.inject.Singleton
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DataManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppStateManager(
|
||||
application: Application,
|
||||
): AppStateManager = AppStateManagerImpl(application = application)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBitwardenPackageManager(
|
||||
|
||||
+16
-6
@@ -1,12 +1,12 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager
|
||||
package com.bitwarden.data.manager.appstate
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import app.cash.turbine.test
|
||||
import com.bitwarden.core.data.util.FakeLifecycleOwner
|
||||
import com.x8bit.bitwarden.data.autofill.util.createdForAutofill
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
|
||||
import com.bitwarden.data.autofill.util.createdForAutofill
|
||||
import com.bitwarden.data.manager.appstate.model.AppCreationState
|
||||
import com.bitwarden.data.manager.appstate.model.AppForegroundState
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
@@ -15,7 +15,9 @@ import io.mockk.runs
|
||||
import io.mockk.slot
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class AppStateManagerTest {
|
||||
@@ -31,6 +33,16 @@ class AppStateManagerTest {
|
||||
processLifecycleOwner = fakeLifecycleOwner,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(Activity::createdForAutofill)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(Activity::createdForAutofill)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `appForegroundStateFlow should emit whenever the underlying ProcessLifecycleOwner receives start and stop events`() =
|
||||
@@ -62,7 +74,6 @@ class AppStateManagerTest {
|
||||
@Test
|
||||
fun `appCreatedStateFlow should emit whenever the underlying activities are all destroyed or a creation event occurs`() =
|
||||
runTest {
|
||||
mockkStatic(Activity::createdForAutofill)
|
||||
val activity = mockk<Activity> {
|
||||
every { isChangingConfigurations } returns false
|
||||
every { createdForAutofill } returns false
|
||||
@@ -83,6 +94,5 @@ class AppStateManagerTest {
|
||||
activityLifecycleCallbacks.captured.onActivityDestroyed(activity)
|
||||
assertEquals(AppCreationState.Destroyed, awaitItem())
|
||||
}
|
||||
unmockkStatic(Activity::createdForAutofill)
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -1,8 +1,7 @@
|
||||
package com.x8bit.bitwarden.data.platform.manager.util
|
||||
package com.bitwarden.data.manager.appstate
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.manager.AppStateManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppCreationState
|
||||
import com.x8bit.bitwarden.data.platform.manager.model.AppForegroundState
|
||||
import com.bitwarden.data.manager.appstate.model.AppCreationState
|
||||
import com.bitwarden.data.manager.appstate.model.AppForegroundState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
Reference in new issue
Block a user