1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-07 21:37:02 +09:00

DROID-3629 Onboarding | Local mode status (#2400)

This commit is contained in:
Konstantin Ivanov 2025-05-14 12:55:10 +02:00 committed by konstantiniiv
parent a52bd6f4ae
commit 9da4bc43b7
12 changed files with 212 additions and 20 deletions

View file

@ -6,6 +6,8 @@ import com.anytypeio.anytype.di.common.ComponentDependencies
import com.anytypeio.anytype.domain.auth.interactor.GetMnemonic
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.device.NetworkConnectionStatus
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.presentation.onboarding.signup.OnboardingMnemonicViewModel
import dagger.Binds
import dagger.Component
@ -57,6 +59,8 @@ interface OnboardingMnemonicDependencies : ComponentDependencies {
fun authRepository(): AuthRepository
fun analytics(): Analytics
fun config(): ConfigStorage
fun networkModeProvider(): NetworkModeProvider
fun networkConnectionStatus(): NetworkConnectionStatus
}
@Scope

View file

@ -2,8 +2,6 @@ package com.anytypeio.anytype.di.main
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.anytypeio.anytype.app.DefaultAppActionManager
import com.anytypeio.anytype.app.DefaultInitialParamsProvider
import com.anytypeio.anytype.core_utils.tools.ThreadInfo
@ -49,10 +47,10 @@ import com.anytypeio.anytype.middleware.interactor.ProtobufConverterProvider
import com.anytypeio.anytype.middleware.service.MiddlewareService
import com.anytypeio.anytype.middleware.service.MiddlewareServiceImplementation
import com.anytypeio.anytype.persistence.db.AnytypeDatabase
import com.anytypeio.anytype.persistence.networkmode.NetworkModeProvider
import com.anytypeio.anytype.device.providers.AppDefaultDateFormatProvider
import com.anytypeio.anytype.device.providers.AppDefaultDateFormatProviderImpl
import com.anytypeio.anytype.domain.misc.LocaleProvider
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.persistence.repo.DefaultAuthCache
import com.anytypeio.anytype.persistence.repo.DefaultDebugSettingsCache
import com.anytypeio.anytype.persistence.repo.DefaultUserSettingsCache
@ -62,10 +60,8 @@ import com.anytypeio.anytype.security.KeystoreManager
import dagger.Binds
import dagger.Module
import dagger.Provides
import java.security.GeneralSecurityException
import javax.inject.Named
import javax.inject.Singleton
import timber.log.Timber
@Module(includes = [DataModule.Bindings::class])
object DataModule {

View file

@ -7,9 +7,9 @@ import com.anytypeio.anytype.device.network_type.NetworkConnectionStatusImpl
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import com.anytypeio.anytype.domain.device.NetworkConnectionStatus
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider
import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider.NetworkModeConstants.NAMED_NETWORK_MODE_PREFS
import com.anytypeio.anytype.persistence.networkmode.NetworkModeProvider
import dagger.Module
import dagger.Provides
import javax.inject.Named

View file

@ -24,6 +24,8 @@ class NetworkConnectionStatusImpl(
context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
private var isMonitoring = false
private var currentNetworkType: DeviceNetworkType = DeviceNetworkType.NOT_CONNECTED
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
// Called when the network is available
@ -74,6 +76,7 @@ class NetworkConnectionStatusImpl(
private fun updateNetworkState(networkCapabilities: NetworkCapabilities?) {
coroutineScope.launch {
val networkType = mapNetworkType(networkCapabilities)
currentNetworkType = networkType
withContext(dispatchers.io) {
try {
blockRepository.setDeviceNetworkState(networkType)
@ -102,4 +105,11 @@ class NetworkConnectionStatusImpl(
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> DeviceNetworkType.CELLULAR
else -> DeviceNetworkType.NOT_CONNECTED
}
override fun getCurrentNetworkType(): DeviceNetworkType {
if (!isMonitoring) {
return mapNetworkType(getNetworkCapabilities())
}
return currentNetworkType
}
}

View file

@ -1,6 +1,9 @@
package com.anytypeio.anytype.domain.device
import com.anytypeio.anytype.core_models.DeviceNetworkType
interface NetworkConnectionStatus {
fun start()
fun stop()
fun getCurrentNetworkType(): DeviceNetworkType
}

View file

@ -0,0 +1,25 @@
package com.anytypeio.anytype.domain.network
import com.anytypeio.anytype.core_models.NetworkModeConfig
/**
* Interface for providing and managing network mode configuration
*/
interface NetworkModeProvider {
/**
* Set a new network mode configuration
* @param networkModeConfig The network mode configuration to set
*/
fun set(networkModeConfig: NetworkModeConfig)
/**
* Get the current network mode configuration
* @return The current NetworkModeConfig
*/
fun get(): NetworkModeConfig
/**
* Clear the current network mode configuration
*/
fun clear()
}

View file

@ -11,6 +11,7 @@ dependencies {
implementation project(':data')
implementation project(':core-models')
implementation project(':device')
implementation project(':domain')
implementation libs.kotlin
implementation libs.coroutinesAndroid

View file

@ -6,17 +6,12 @@ import com.anytypeio.anytype.core_models.NetworkModeConfig
import com.anytypeio.anytype.core_models.NetworkModeConstants.NETWORK_MODE_CUSTOM
import com.anytypeio.anytype.core_models.NetworkModeConstants.NETWORK_MODE_DEFAULT
import com.anytypeio.anytype.core_models.NetworkModeConstants.NETWORK_MODE_LOCAL
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider.NetworkModeConstants.NETWORK_MODE_APP_FILE_PATH_PREF
import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider.NetworkModeConstants.NETWORK_MODE_PREF
import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider.NetworkModeConstants.NETWORK_MODE_USER_FILE_PATH_PREF
import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider.NetworkModeConstants.USE_RESERVE_MULTIPLEX_LIBRARY_PREF
interface NetworkModeProvider {
fun set(networkModeConfig: NetworkModeConfig)
fun get(): NetworkModeConfig
fun clear()
}
class DefaultNetworkModeProvider(private val sharedPreferences: SharedPreferences) :
NetworkModeProvider {

View file

@ -4,10 +4,10 @@ import android.content.SharedPreferences
import com.anytypeio.anytype.core_models.NetworkModeConfig
import com.anytypeio.anytype.data.auth.model.AccountEntity
import com.anytypeio.anytype.data.auth.repo.AuthCache
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.persistence.db.AnytypeDatabase
import com.anytypeio.anytype.persistence.mapper.toEntity
import com.anytypeio.anytype.persistence.mapper.toTable
import com.anytypeio.anytype.persistence.networkmode.NetworkModeProvider
class DefaultAuthCache(
private val db: AnytypeDatabase,

View file

@ -4,9 +4,9 @@ import android.content.Context
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.test.platform.app.InstrumentationRegistry
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.persistence.db.AnytypeDatabase
import com.anytypeio.anytype.persistence.networkmode.DefaultNetworkModeProvider
import com.anytypeio.anytype.persistence.networkmode.NetworkModeProvider
import com.anytypeio.anytype.persistence.repo.DefaultAuthCache
import com.anytypeio.anytype.test_utils.MockDataFactory
import kotlin.test.assertEquals

View file

@ -5,11 +5,14 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.analytics.base.EventsDictionary
import com.anytypeio.anytype.core_models.DeviceNetworkType
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.NetworkModeConfig
import com.anytypeio.anytype.core_models.NetworkMode
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.auth.interactor.GetMnemonic
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.device.NetworkConnectionStatus
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.presentation.extension.sendAnalyticsOnboardingClickEvent
import com.anytypeio.anytype.presentation.extension.sendAnalyticsOnboardingScreenEvent
import com.anytypeio.anytype.presentation.extension.sendOpenAccountEvent
@ -22,7 +25,9 @@ import timber.log.Timber
class OnboardingMnemonicViewModel @Inject constructor(
private val getMnemonic: GetMnemonic,
private val analytics: Analytics,
private val configStorage: ConfigStorage
private val configStorage: ConfigStorage,
private val networkModeProvider: NetworkModeProvider,
private val networkConnectionStatus: NetworkConnectionStatus
) : ViewModel() {
val state = MutableStateFlow<State>(State.Idle(""))
@ -128,9 +133,13 @@ class OnboardingMnemonicViewModel @Inject constructor(
}
}
private fun shouldShowEmail(): Boolean {
//todo: update with Local Only config
return true
fun shouldShowEmail(): Boolean {
val networkStatus = networkConnectionStatus.getCurrentNetworkType()
if (networkStatus == DeviceNetworkType.NOT_CONNECTED) {
Timber.i("Network is not connected, skipping email screen")
return false
}
return networkModeProvider.get().networkMode != NetworkMode.LOCAL
}
private suspend fun proceedWithMnemonicPhrase() {
@ -156,13 +165,17 @@ class OnboardingMnemonicViewModel @Inject constructor(
private val getMnemonic: GetMnemonic,
private val analytics: Analytics,
private val configStorage: ConfigStorage,
private val networkModeProvider: NetworkModeProvider,
private val networkConnectionStatus: NetworkConnectionStatus
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return OnboardingMnemonicViewModel(
getMnemonic = getMnemonic,
analytics = analytics,
configStorage = configStorage
configStorage = configStorage,
networkModeProvider = networkModeProvider,
networkConnectionStatus = networkConnectionStatus
) as T
}
}

View file

@ -0,0 +1,145 @@
package com.anytypeio.anytype.presentation.onboarding.signup
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.DeviceNetworkType
import com.anytypeio.anytype.core_models.NetworkMode
import com.anytypeio.anytype.core_models.NetworkModeConfig
import com.anytypeio.anytype.domain.auth.interactor.GetMnemonic
import com.anytypeio.anytype.domain.config.ConfigStorage
import com.anytypeio.anytype.domain.device.NetworkConnectionStatus
import com.anytypeio.anytype.domain.network.NetworkModeProvider
import com.anytypeio.anytype.presentation.util.DefaultCoroutineTestRule
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.stub
class OnboardingMnemonicViewModelTest {
@OptIn(ExperimentalCoroutinesApi::class)
@get:Rule
val coroutineTestRule = DefaultCoroutineTestRule()
@Mock
private lateinit var getMnemonic: GetMnemonic
@Mock
private lateinit var analytics: Analytics
@Mock
private lateinit var configStorage: ConfigStorage
@Mock
private lateinit var networkConnectionStatus: NetworkConnectionStatus
@Mock
private lateinit var networkModeProvider: NetworkModeProvider
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
}
@Test
fun `shouldShowEmail returns false when network is not connected`() {
// Given
networkConnectionStatus.stub {
on { getCurrentNetworkType() }.thenReturn(DeviceNetworkType.NOT_CONNECTED)
}
networkModeProvider.stub {
on { get() }.thenReturn(
NetworkModeConfig(
networkMode = NetworkMode.DEFAULT
)
)
}
// When
val viewModel = createViewModel()
val result = viewModel.shouldShowEmail()
// Then
assertFalse(result)
}
@Test
fun `shouldShowEmail returns false when network mode is LOCAL`() {
// Given
networkConnectionStatus.stub {
on { getCurrentNetworkType() }.thenReturn(DeviceNetworkType.WIFI)
}
networkModeProvider.stub {
on { get() }.thenReturn(
NetworkModeConfig(
networkMode = NetworkMode.LOCAL
)
)
}
// When
val viewModel = createViewModel()
val result = viewModel.shouldShowEmail()
// Then
assertFalse(result)
}
@Test
fun `shouldShowEmail returns true when connected to network and network mode is not LOCAL`() {
// Given
networkConnectionStatus.stub {
on { getCurrentNetworkType() }.thenReturn(DeviceNetworkType.WIFI)
}
networkModeProvider.stub {
on { get() }.thenReturn(
NetworkModeConfig(
networkMode = NetworkMode.DEFAULT
)
)
}
// When
val viewModel = createViewModel()
val result = viewModel.shouldShowEmail()
// Then
assertTrue(result)
}
@Test
fun `shouldShowEmail returns true with cellular network and non-LOCAL mode`() {
// Given
networkConnectionStatus.stub {
on { getCurrentNetworkType() }.thenReturn(DeviceNetworkType.CELLULAR)
}
networkModeProvider.stub {
on { get() }.thenReturn(
NetworkModeConfig(
networkMode = NetworkMode.DEFAULT
)
)
}
// When
val viewModel = createViewModel()
val result = viewModel.shouldShowEmail()
// Then
assertTrue(result)
}
private fun createViewModel(): OnboardingMnemonicViewModel {
return OnboardingMnemonicViewModel(
getMnemonic = getMnemonic,
analytics = analytics,
configStorage = configStorage,
networkModeProvider = networkModeProvider,
networkConnectionStatus = networkConnectionStatus
)
}
}