diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt index e754cbf75f..ade1527b09 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingMnemonicDI.kt @@ -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 diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt index f96979503f..2ea4ced4e6 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/DataModule.kt @@ -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 { diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt b/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt index 82ba8ea05a..c1b08b3cc9 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt @@ -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 diff --git a/device/src/main/java/com/anytypeio/anytype/device/network_type/NetworkConnectionStatus.kt b/device/src/main/java/com/anytypeio/anytype/device/network_type/NetworkConnectionStatus.kt index e104163127..4523995766 100644 --- a/device/src/main/java/com/anytypeio/anytype/device/network_type/NetworkConnectionStatus.kt +++ b/device/src/main/java/com/anytypeio/anytype/device/network_type/NetworkConnectionStatus.kt @@ -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 + } } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/device/NetworkConnectionStatus.kt b/domain/src/main/java/com/anytypeio/anytype/domain/device/NetworkConnectionStatus.kt index f34257caa5..0175b12c02 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/device/NetworkConnectionStatus.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/device/NetworkConnectionStatus.kt @@ -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 } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/network/NetworkModeProvider.kt b/domain/src/main/java/com/anytypeio/anytype/domain/network/NetworkModeProvider.kt new file mode 100644 index 0000000000..0b4befd138 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/network/NetworkModeProvider.kt @@ -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() +} \ No newline at end of file diff --git a/persistence/build.gradle b/persistence/build.gradle index d950695cc8..efde931a2d 100644 --- a/persistence/build.gradle +++ b/persistence/build.gradle @@ -11,6 +11,7 @@ dependencies { implementation project(':data') implementation project(':core-models') implementation project(':device') + implementation project(':domain') implementation libs.kotlin implementation libs.coroutinesAndroid diff --git a/persistence/src/main/java/com/anytypeio/anytype/persistence/networkmode/NetworkModeProvider.kt b/persistence/src/main/java/com/anytypeio/anytype/persistence/networkmode/NetworkModeProvider.kt index 88986d3d06..53b7316e6c 100644 --- a/persistence/src/main/java/com/anytypeio/anytype/persistence/networkmode/NetworkModeProvider.kt +++ b/persistence/src/main/java/com/anytypeio/anytype/persistence/networkmode/NetworkModeProvider.kt @@ -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 { diff --git a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt index a1ac72cc8f..32f948e9c8 100644 --- a/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt +++ b/persistence/src/main/java/com/anytypeio/anytype/persistence/repo/DefaultAuthCache.kt @@ -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, diff --git a/persistence/src/test/java/com/anytypeio/anytype/persistence/DefaultAuthCacheTest.kt b/persistence/src/test/java/com/anytypeio/anytype/persistence/DefaultAuthCacheTest.kt index 16496bc819..b1b6094585 100644 --- a/persistence/src/test/java/com/anytypeio/anytype/persistence/DefaultAuthCacheTest.kt +++ b/persistence/src/test/java/com/anytypeio/anytype/persistence/DefaultAuthCacheTest.kt @@ -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 diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt index 4023248ca9..17a7ea06f4 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModel.kt @@ -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.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 create(modelClass: Class): T { return OnboardingMnemonicViewModel( getMnemonic = getMnemonic, analytics = analytics, - configStorage = configStorage + configStorage = configStorage, + networkModeProvider = networkModeProvider, + networkConnectionStatus = networkConnectionStatus ) as T } } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModelTest.kt new file mode 100644 index 0000000000..8318c4ca98 --- /dev/null +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingMnemonicViewModelTest.kt @@ -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 + ) + } +} \ No newline at end of file