From f0b542261d49eda27ef80037e51efa82f27b1137 Mon Sep 17 00:00:00 2001 From: Konstantin Ivanov <54908981+konstantiniiv@users.noreply.github.com> Date: Sat, 9 Dec 2023 14:53:19 +0100 Subject: [PATCH] DROID-1602 Self-hosting | Auth (#659) Co-authored-by: Evgenii Kozlov --- .../anytype/di/common/ComponentManager.kt | 7 + .../anytype/di/feature/AppPreferencesDi.kt | 61 ++++++ .../anytypeio/anytype/di/feature/AuthDI.kt | 0 .../signup/OnboardingSoulCreationDI.kt | 6 +- .../anytypeio/anytype/di/main/DataModule.kt | 7 +- .../anytype/di/main/MainComponent.kt | 12 +- .../anytype/di/main/NetworkModeModule.kt | 31 +++ .../anytype/ui/editor/PickerDelegate.kt | 10 +- .../ui/settings/system/PreferenceFragment.kt | 132 +++++++++++- app/src/main/res/xml/preferences.xml | 4 +- .../anytypeio/anytype/core_models/Command.kt | 15 ++ .../anytype/core_models/NetworkMode.kt | 11 + .../core_models/NetworkModeConstants.kt | 7 + .../anytype/core_utils/const/FileConstants.kt | 1 + .../core_utils/ext/AndroidExtension.kt | 4 +- .../anytype/core_utils/ext/FilePickerUtils.kt | 11 +- .../anytype/core_utils/ext/Mimetype.kt | 3 +- .../anytype/data/auth/repo/AuthCache.kt | 5 + .../data/auth/repo/AuthCacheDataStore.kt | 21 +- .../data/auth/repo/AuthDataRepository.kt | 42 ++-- .../anytype/data/auth/repo/AuthDataStore.kt | 14 +- .../anytype/data/auth/repo/AuthRemote.kt | 9 +- .../data/auth/repo/AuthRemoteDataStore.kt | 27 +-- .../anytype/data/AuthDataRepositoryTest.kt | 40 ++-- .../domain/auth/interactor/CreateAccount.kt | 24 ++- .../domain/auth/interactor/LaunchAccount.kt | 13 +- .../domain/auth/interactor/ResumeAccount.kt | 13 +- .../domain/auth/interactor/SelectAccount.kt | 11 +- .../domain/auth/repo/AuthRepository.kt | 14 +- .../domain/networkmode/GetNetworkMode.kt | 17 ++ .../domain/networkmode/SetNetworkMode.kt | 19 ++ .../anytype/domain/auth/CreateAccountTest.kt | 46 +++- .../anytype/domain/auth/StartAccountTest.kt | 197 ++++++++++++++++-- .../domain/common/DefaultCoroutineTestRule.kt | 29 +++ localization/src/main/res/values/strings.xml | 15 ++ .../anytype/middleware/auth/AuthMiddleware.kt | 15 +- .../middleware/interactor/Middleware.kt | 48 +++-- .../anytype/middleware/mappers/Alias.kt | 1 + .../mappers/ToMiddlewareModelMappers.kt | 7 + .../networkmode/NetworkModeProvider.kt | 70 +++++++ .../persistence/repo/DefaultAuthCache.kt | 10 +- .../persistence/DefaultAuthCacheTest.kt | 11 +- .../presentation/editor/EditorViewModel.kt | 6 +- .../OnboardingSetProfileNameViewModel.kt | 49 ++--- .../signup/OnboardingVoidViewModel.kt | 27 +-- .../sets/RelationValueBaseViewModel.kt | 6 +- .../settings/PreferencesViewModel.kt | 120 +++++++++++ .../presentation/util/CopyFileToCache.kt | 171 ++++++++++++++- 48 files changed, 1196 insertions(+), 223 deletions(-) create mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/AppPreferencesDi.kt create mode 100644 app/src/main/java/com/anytypeio/anytype/di/feature/AuthDI.kt create mode 100644 app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt create mode 100644 core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkMode.kt create mode 100644 core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkModeConstants.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/networkmode/GetNetworkMode.kt create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/networkmode/SetNetworkMode.kt create mode 100644 domain/src/test/java/com/anytypeio/anytype/domain/common/DefaultCoroutineTestRule.kt create mode 100644 persistence/src/main/java/com/anytypeio/anytype/persistence/networkmode/NetworkModeProvider.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/settings/PreferencesViewModel.kt diff --git a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt index 21ae53a154..59176c7a62 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/common/ComponentManager.kt @@ -11,6 +11,7 @@ import com.anytypeio.anytype.di.feature.AddObjectRelationValueModule import com.anytypeio.anytype.di.feature.CreateBookmarkModule import com.anytypeio.anytype.di.feature.CreateDataViewViewerModule import com.anytypeio.anytype.di.feature.CreateObjectModule +import com.anytypeio.anytype.di.feature.DaggerAppPreferencesComponent import com.anytypeio.anytype.di.feature.DaggerBacklinkOrAddToObjectComponent import com.anytypeio.anytype.di.feature.DaggerSplashComponent import com.anytypeio.anytype.di.feature.DataViewRelationValueModule @@ -934,6 +935,12 @@ class ComponentManager( .create(findComponentDependencies()) } + val appPreferencesComponent = Component { + DaggerAppPreferencesComponent + .factory() + .create(findComponentDependencies()) + } + class Component(private val builder: () -> T) { diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/AppPreferencesDi.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/AppPreferencesDi.kt new file mode 100644 index 0000000000..994eb88085 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/AppPreferencesDi.kt @@ -0,0 +1,61 @@ +package com.anytypeio.anytype.di.feature + +import android.content.Context +import com.anytypeio.anytype.core_utils.di.scope.PerScreen +import com.anytypeio.anytype.di.common.ComponentDependencies +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.networkmode.GetNetworkMode +import com.anytypeio.anytype.domain.networkmode.SetNetworkMode +import com.anytypeio.anytype.presentation.settings.PreferencesViewModel +import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory +import com.anytypeio.anytype.presentation.util.NetworkModeCopyFileToCacheDirectory +import com.anytypeio.anytype.ui.settings.system.PreferenceFragment +import dagger.Component +import dagger.Module +import dagger.Provides + +@Component( + dependencies = [AppPreferencesDependencies::class], + modules = [AppPreferencesModule::class] +) +@PerScreen +interface AppPreferencesComponent { + + @Component.Factory + interface Factory { + fun create(dependency: AppPreferencesDependencies): AppPreferencesComponent + } + + fun inject(fragment: PreferenceFragment) +} + +@Module +object AppPreferencesModule { + + @JvmStatic + @Provides + @PerScreen + fun provideCopyFileToCache( + context: Context + ): CopyFileToCacheDirectory = NetworkModeCopyFileToCacheDirectory(context) + + @JvmStatic + @Provides + @PerScreen + fun provideViewModelFactory( + copyFileToCacheDirectory: CopyFileToCacheDirectory, + getNetworkMode: GetNetworkMode, + setNetworkMode: SetNetworkMode + ): PreferencesViewModel.Factory = PreferencesViewModel.Factory( + copyFileToCacheDirectory = copyFileToCacheDirectory, + getNetworkMode = getNetworkMode, + setNetworkMode = setNetworkMode + ) +} + +interface AppPreferencesDependencies : ComponentDependencies { + fun context(): Context + fun dispatchers(): AppCoroutineDispatchers + fun authRepository(): AuthRepository +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/AuthDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/AuthDI.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt index 4d9957bb14..e81ab4003d 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/onboarding/signup/OnboardingSoulCreationDI.kt @@ -59,11 +59,13 @@ object OnboardingSoulCreationModule { fun provideCreateAccountUseCase( authRepository: AuthRepository, configStorage: ConfigStorage, - metricsProvider: MetricsProvider + metricsProvider: MetricsProvider, + dispatchers: AppCoroutineDispatchers ): CreateAccount = CreateAccount( repository = authRepository, configStorage = configStorage, - metricsProvider = metricsProvider + metricsProvider = metricsProvider, + dispatcher = dispatchers ) @JvmStatic 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 7ad46ad87e..d55a07651c 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 @@ -51,6 +51,7 @@ 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.persistence.repo.DefaultAuthCache import com.anytypeio.anytype.persistence.repo.DefaultDebugSettingsCache import com.anytypeio.anytype.persistence.repo.DefaultUserSettingsCache @@ -113,12 +114,14 @@ object DataModule { fun provideAuthCache( db: AnytypeDatabase, @Named("default") defaultPrefs: SharedPreferences, - @Named("encrypted") encryptedPrefs: SharedPreferences + @Named("encrypted") encryptedPrefs: SharedPreferences, + networkModeProvider: NetworkModeProvider ): AuthCache { return DefaultAuthCache( db = db, defaultPrefs = defaultPrefs, - encryptedPrefs = encryptedPrefs + encryptedPrefs = encryptedPrefs, + networkModeProvider = networkModeProvider ) } diff --git a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt index 43a1b4068c..0aa7758b00 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/main/MainComponent.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.di.main import com.anytypeio.anytype.app.AndroidApplication import com.anytypeio.anytype.di.common.ComponentDependencies import com.anytypeio.anytype.di.common.ComponentDependenciesKey +import com.anytypeio.anytype.di.feature.AppPreferencesDependencies import com.anytypeio.anytype.di.feature.BacklinkOrAddToObjectDependencies import com.anytypeio.anytype.di.feature.CreateBookmarkSubComponent import com.anytypeio.anytype.di.feature.CreateObjectSubComponent @@ -70,7 +71,8 @@ import javax.inject.Singleton LocalNetworkProviderModule::class, SubscriptionsModule::class, CrashReportingModule::class, - TemplatesModule::class + TemplatesModule::class, + NetworkModeModule::class ] ) interface MainComponent : @@ -100,7 +102,8 @@ interface MainComponent : CreateSpaceDependencies, SpaceSettingsDependencies, CreateObjectOfTypeDependencies, - SpacesStorageDependencies + SpacesStorageDependencies, + AppPreferencesDependencies { fun inject(app: AndroidApplication) @@ -267,4 +270,9 @@ private abstract class ComponentDependenciesModule private constructor() { @IntoMap @ComponentDependenciesKey(SpacesStorageDependencies::class) abstract fun provideSpacesStorageDependencies(component: MainComponent): ComponentDependencies + + @Binds + @IntoMap + @ComponentDependenciesKey(AppPreferencesDependencies::class) + abstract fun providePreferencesDependencies(component: MainComponent): ComponentDependencies } \ No newline at end of file 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 new file mode 100644 index 0000000000..6de63f41dc --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/di/main/NetworkModeModule.kt @@ -0,0 +1,31 @@ +package com.anytypeio.anytype.di.main + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +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 +import javax.inject.Singleton + +@Module +object NetworkModeModule { + + @JvmStatic + @Provides + @Singleton + @Named(NAMED_NETWORK_MODE_PREFS) + fun provideNetworkModeSharedPreferences( + context: Context + ): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + + @JvmStatic + @Provides + @Singleton + fun provider( + @Named(NAMED_NETWORK_MODE_PREFS) sharedPreferences: SharedPreferences + ): NetworkModeProvider = DefaultNetworkModeProvider(sharedPreferences) +} \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/PickerDelegate.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/PickerDelegate.kt index d032a603b9..2615544048 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/PickerDelegate.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/PickerDelegate.kt @@ -41,7 +41,7 @@ interface PickerDelegate : PickiTCallbacks { fun clearOnCopyFile() sealed class Actions { - data class OnStartCopyFileToCacheDir(val uri: Uri) : Actions() + data class OnStartCopyFileToCacheDir(val uri: Uri, val path: String? = null) : Actions() object OnCancelCopyFileToCacheDir : Actions() data class OnProceedWithFilePath(val filePath: String) : Actions() data class OnPickedDocImageFromDevice(val ctx: String, val filePath: String) : Actions() @@ -84,6 +84,14 @@ interface PickerDelegate : PickiTCallbacks { pickiT.getPath(uri, Build.VERSION.SDK_INT) } } + FileConstants.REQUEST_NETWORK_MODE_CODE -> { + data?.data?.let { uri -> + actions(Actions.OnStartCopyFileToCacheDir(uri)) + } ?: run { + Timber.e("onActivityResult error, data is null") + fragment.toast("Error while getting file") + } + } else -> { Timber.e("onActivityResult error, Unknown Request Code:$requestCode") fragment.toast("Unknown Request Code:$requestCode") diff --git a/app/src/main/java/com/anytypeio/anytype/ui/settings/system/PreferenceFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/settings/system/PreferenceFragment.kt index 770cd68f38..381be088ad 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/settings/system/PreferenceFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/settings/system/PreferenceFragment.kt @@ -1,12 +1,142 @@ package com.anytypeio.anytype.ui.settings.system +import android.app.Activity +import android.content.Context +import android.content.Intent import android.os.Bundle +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.preference.DropDownPreference +import androidx.preference.ListPreference +import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.anytypeio.anytype.R +import com.anytypeio.anytype.core_models.NetworkMode +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.core_utils.ext.Mimetype +import com.anytypeio.anytype.di.common.componentManager +import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_NETWORK_MODE_CODE +import com.anytypeio.anytype.core_utils.ext.subscribe +import com.anytypeio.anytype.presentation.settings.PreferencesViewModel +import com.anytypeio.anytype.ui.editor.PickerDelegate +import javax.inject.Inject class PreferenceFragment : PreferenceFragmentCompat() { + private lateinit var pickerDelegate: PickerDelegate + private lateinit var filePathPreference: Preference + private lateinit var networkModePreference: DropDownPreference + + @Inject + lateinit var factory: PreferencesViewModel.Factory + private val vm by viewModels { factory } + + override fun onAttach(context: Context) { + injectDependencies() + super.onAttach(context) + pickerDelegate = PickerDelegate.Impl(this) { actions -> + when (actions) { + is PickerDelegate.Actions.OnProceedWithFilePath -> { + vm.onProceedWithFilePath(actions.filePath) + } + PickerDelegate.Actions.OnCancelCopyFileToCacheDir -> { + vm.onCancelCopyFileToCacheDir() + } + is PickerDelegate.Actions.OnPickedDocImageFromDevice -> { + vm.onPickedDocImageFromDevice(actions.ctx, actions.filePath) + } + is PickerDelegate.Actions.OnStartCopyFileToCacheDir -> { + vm.onStartCopyFileToCacheDir(actions.uri) + } + } + } + pickerDelegate.initPicker("") + } + + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_OK) { + pickerDelegate.resolveActivityResult(requestCode, resultCode, data) + } else { + super.onActivityResult(requestCode, resultCode, data) + } + } + + override fun onStart() { + super.onStart() + with(lifecycleScope) { + subscribe(vm.networkModeState) { state -> + when (state.networkMode) { + NetworkMode.DEFAULT -> { + networkModePreference.value = NETWORK_MODE_DEFAULT + filePathPreference.isVisible = false + } + NetworkMode.LOCAL -> { + networkModePreference.value = NETWORK_MODE_LOCAL + filePathPreference.isVisible = false + } + NetworkMode.CUSTOM -> { + networkModePreference.value = NETWORK_MODE_CUSTOM + filePathPreference.isVisible = true + filePathPreference.summary = state.userFilePath + } + } + } + } + vm.onStart() + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.preferences, rootKey) + val context = preferenceManager.context + val screen = preferenceManager.createPreferenceScreen(context) + networkModePreference = DropDownPreference(context).apply { + summaryProvider = ListPreference.SimpleSummaryProvider.getInstance() + isSingleLineTitle = true + isIconSpaceReserved = false + title = getString(R.string.settings_network_mode) + setEntries(R.array.settings_networks_entries) + setEntryValues(R.array.settings_networks_entries_values) + onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + vm.proceedWithNetworkMode(newValue as String) + true + } + } + screen.addPreference(networkModePreference) + filePathPreference = Preference(context).apply { + isIconSpaceReserved = false + title = getString(R.string.settings_network_configuration_file) + isSingleLineTitle = true + summary = getString(R.string.settings_network_configuration_file_choose) + } + filePathPreference.setOnPreferenceClickListener { + openFilePicker() + true + } + screen.addPreference(filePathPreference) + preferenceScreen = screen + } + + private fun openFilePicker() { + pickerDelegate.openFilePicker(Mimetype.MIME_TEXT_PLAIN, REQUEST_NETWORK_MODE_CODE) + } + + override fun onDestroyView() { + pickerDelegate.clearPickit() + super.onDestroyView() + } + + override fun onDestroy() { + releaseDependencies() + super.onDestroy() + } + + private fun injectDependencies() { + componentManager().appPreferencesComponent.get().inject(this) + } + + private fun releaseDependencies() { + componentManager().appPreferencesComponent.release() } } \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 1c93420c83..11b11e5549 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -1,7 +1,9 @@ - + + \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt index 99b8d5cdbc..09ce0da4f7 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt @@ -5,6 +5,21 @@ import com.anytypeio.anytype.core_models.primitives.TypeKey sealed class Command { + data class AccountCreate( + val name: String, + val avatarPath: String?, + val icon: Int, + val networkMode: NetworkMode = NetworkMode.DEFAULT, + val networkConfigFilePath: String? = null + ) : Command() + + data class AccountSelect( + val id: String, + val path: String, + val networkMode: NetworkMode = NetworkMode.DEFAULT, + val networkConfigFilePath: String? = null + ) : Command() + class UploadFile( val path: String, val type: Block.Content.File.Type? diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkMode.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkMode.kt new file mode 100644 index 0000000000..dc5b83b5cc --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkMode.kt @@ -0,0 +1,11 @@ +package com.anytypeio.anytype.core_models + +enum class NetworkMode { + DEFAULT, LOCAL, CUSTOM +} + +data class NetworkModeConfig( + val networkMode: NetworkMode = NetworkMode.DEFAULT, + val userFilePath: String? = null, + val storedFilePath: String? = null +) diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkModeConstants.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkModeConstants.kt new file mode 100644 index 0000000000..9ddce76750 --- /dev/null +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/NetworkModeConstants.kt @@ -0,0 +1,7 @@ +package com.anytypeio.anytype.core_models + +object NetworkModeConstants { + const val NETWORK_MODE_LOCAL = "local" + const val NETWORK_MODE_DEFAULT = "default" + const val NETWORK_MODE_CUSTOM = "custom" +} \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/FileConstants.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/FileConstants.kt index 54e0f8dd5e..3b2e61f533 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/FileConstants.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/const/FileConstants.kt @@ -5,4 +5,5 @@ object FileConstants { const val REQUEST_FILE_SAF_CODE = 2211 const val REQUEST_MEDIA_CODE = 2212 const val REQUEST_PROFILE_IMAGE_CODE = 2213 + const val REQUEST_NETWORK_MODE_CODE = 2214 } \ No newline at end of file diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt index c3f208e4c2..7dd3a6b6dc 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/AndroidExtension.kt @@ -283,8 +283,8 @@ fun String.normalizeUrl(): String = */ fun Fragment.startFilePicker(mime: Mimetype, requestCode: Int? = null) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val intent = Intent(Intent.ACTION_GET_CONTENT).apply { - putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false) + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) type = mime.value } val code = if (mime == Mimetype.MIME_FILE_ALL) { diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt index 34c08d4f43..ad64e4b2fd 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/FilePickerUtils.kt @@ -14,7 +14,7 @@ object FilePickerUtils { Mimetype.MIME_VIDEO_ALL -> context.isPermissionGranted(getPermissionToRequestForVideos()) Mimetype.MIME_IMAGE_ALL -> context.isPermissionGranted(getPermissionToRequestForImages()) Mimetype.MIME_IMAGE_AND_VIDEO -> context.isPermissionGranted(getPermissionToRequestForImagesAndVideos()) - Mimetype.MIME_FILE_ALL -> { + Mimetype.MIME_FILE_ALL, Mimetype.MIME_TEXT_PLAIN -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { true } else { @@ -36,27 +36,28 @@ object FilePickerUtils { Mimetype.MIME_IMAGE_ALL -> getPermissionToRequestForImages() Mimetype.MIME_FILE_ALL -> getPermissionToRequestForFiles() Mimetype.MIME_IMAGE_AND_VIDEO -> getPermissionToRequestForImagesAndVideos() + Mimetype.MIME_TEXT_PLAIN -> getPermissionToRequestForFiles() } } - fun getPermissionToRequestForImages(): Array = + private fun getPermissionToRequestForImages(): Array = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { arrayOf(Manifest.permission.READ_MEDIA_IMAGES) } else { arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) } - fun getPermissionToRequestForVideos(): Array = + private fun getPermissionToRequestForVideos(): Array = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { arrayOf(Manifest.permission.READ_MEDIA_VIDEO) } else { arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) } - fun getPermissionToRequestForFiles(): Array = + private fun getPermissionToRequestForFiles(): Array = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) - fun getPermissionToRequestForImagesAndVideos(): Array = + private fun getPermissionToRequestForImagesAndVideos(): Array = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { arrayOf(Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_IMAGES) } else { diff --git a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt index 582041df65..8cb5b18394 100644 --- a/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt +++ b/core-utils/src/main/java/com/anytypeio/anytype/core_utils/ext/Mimetype.kt @@ -4,5 +4,6 @@ enum class Mimetype(val value: String) { MIME_VIDEO_ALL("video/*"), MIME_IMAGE_ALL("image/*"), MIME_FILE_ALL("*/*"), - MIME_IMAGE_AND_VIDEO("image/*,video/*") + MIME_IMAGE_AND_VIDEO("image/*,video/*"), + MIME_TEXT_PLAIN("text/plain"), } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCache.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCache.kt index 4f0c243a1b..cdb2c12fc0 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCache.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCache.kt @@ -1,6 +1,8 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.NetworkMode +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.model.AccountEntity interface AuthCache { @@ -21,4 +23,7 @@ interface AuthCache { suspend fun saveLastOpenedObject(id: Id) suspend fun getLastOpenedObject() : Id? suspend fun clearLastOpenedObject() + + suspend fun getNetworkMode(): NetworkModeConfig + suspend fun setNetworkMode(modeConfig: NetworkModeConfig) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt index 94b77f5c48..0e09194d54 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthCacheDataStore.kt @@ -2,25 +2,20 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity import kotlinx.coroutines.flow.Flow class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore { - override suspend fun selectAccount( - id: String, - path: String - ): AccountSetup { + override suspend fun selectAccount(command: Command.AccountSelect): AccountSetup { throw UnsupportedOperationException() } - override suspend fun createAccount( - name: String, - avatarPath: String?, - iconGradientValue: Int - ): AccountSetup { + override suspend fun createAccount(command: Command.AccountCreate): AccountSetup { throw UnsupportedOperationException() } @@ -91,4 +86,12 @@ class AuthCacheDataStore(private val cache: AuthCache) : AuthDataStore { override suspend fun saveLastOpenedObject(id: Id) { cache.saveLastOpenedObject(id) } override suspend fun getLastOpenedObject(): Id? = cache.getLastOpenedObject() override suspend fun clearLastOpenedObject() { cache.clearLastOpenedObject() } + + override suspend fun getNetworkMode(): NetworkModeConfig { + return cache.getNetworkMode() + } + + override suspend fun setNetworkMode(modeConfig: NetworkModeConfig) { + cache.setNetworkMode(modeConfig) + } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt index 7a8e574ab9..226ecd87d4 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataRepository.kt @@ -3,7 +3,9 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.Account import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.mapper.toDomain import com.anytypeio.anytype.data.auth.mapper.toEntity import com.anytypeio.anytype.domain.auth.model.Wallet @@ -25,42 +27,22 @@ class AuthDataRepository( } override suspend fun selectAccount( - id: String, path: String + command: Command.AccountSelect ): AccountSetup { return if (debugConfig.setTimeouts) { withTimeout(DebugConfig.SELECT_ACCOUNT_TIMEOUT) { - factory.remote.selectAccount( - id = id, - path = path - ) + factory.remote.selectAccount(command) } - } else { - factory.remote.selectAccount( - id = id, - path = path - ) - } + } else { factory.remote.selectAccount(command)} } override suspend fun createAccount( - name: String, - avatarPath: String?, - icon: Int + command: Command.AccountCreate ): AccountSetup { return if (debugConfig.setTimeouts) { - withTimeout(DebugConfig.CREATE_ACCOUNT_TIMEOUT) { - factory.remote.createAccount( - name = name, - avatarPath = avatarPath, - iconGradientValue = icon - ) - } + withTimeout(DebugConfig.CREATE_ACCOUNT_TIMEOUT) { factory.remote.createAccount(command)} } else { - factory.remote.createAccount( - name = name, - avatarPath = avatarPath, - iconGradientValue = icon - ) + factory.remote.createAccount(command) } } @@ -120,4 +102,12 @@ class AuthDataRepository( override suspend fun clearLastOpenedObject() { factory.cache.clearLastOpenedObject() } + + override suspend fun getNetworkMode(): NetworkModeConfig { + return factory.cache.getNetworkMode() + } + + override suspend fun setNetworkMode(modeConfig: NetworkModeConfig) { + factory.cache.setNetworkMode(modeConfig) + } } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt index 71421c4830..f6a7e29b7a 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthDataStore.kt @@ -2,20 +2,17 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity import kotlinx.coroutines.flow.Flow interface AuthDataStore { - suspend fun selectAccount(id: String, path: String): AccountSetup - - suspend fun createAccount( - name: String, - avatarPath: String?, - iconGradientValue: Int - ): AccountSetup + suspend fun selectAccount(command: Command.AccountSelect): AccountSetup + suspend fun createAccount(command: Command.AccountCreate): AccountSetup suspend fun deleteAccount() : AccountStatus suspend fun restoreAccount() : AccountStatus @@ -47,4 +44,7 @@ interface AuthDataStore { suspend fun saveLastOpenedObject(id: Id) suspend fun getLastOpenedObject() : Id? suspend fun clearLastOpenedObject() + + suspend fun getNetworkMode(): NetworkModeConfig + suspend fun setNetworkMode(modeConfig: NetworkModeConfig) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt index ec0f295fe3..0bb38a885f 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemote.kt @@ -2,17 +2,14 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity import kotlinx.coroutines.flow.Flow interface AuthRemote { - suspend fun selectAccount(id: String, path: String): AccountSetup - suspend fun createAccount( - name: String, - avatarPath: String?, - iconGradientValue: Int - ): AccountSetup + suspend fun selectAccount(command: Command.AccountSelect): AccountSetup + suspend fun createAccount(command: Command.AccountCreate): AccountSetup suspend fun deleteAccount() : AccountStatus suspend fun restoreAccount() : AccountStatus suspend fun recoverAccount() diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt index dd8a0aed11..d744fc7340 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/AuthRemoteDataStore.kt @@ -2,7 +2,9 @@ package com.anytypeio.anytype.data.auth.repo import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.data.auth.model.AccountEntity import com.anytypeio.anytype.data.auth.model.WalletEntity @@ -11,21 +13,12 @@ class AuthRemoteDataStore( ) : AuthDataStore { override suspend fun selectAccount( - id: String, path: String - ) = authRemote.selectAccount( - id = id, - path = path - ) + command: Command.AccountSelect + ) = authRemote.selectAccount(command) override suspend fun createAccount( - name: String, - avatarPath: String?, - iconGradientValue: Int - ) : AccountSetup = authRemote.createAccount( - name = name, - avatarPath = avatarPath, - iconGradientValue = iconGradientValue - ) + command: Command.AccountCreate + ): AccountSetup = authRemote.createAccount(command) override suspend fun deleteAccount(): AccountStatus = authRemote.deleteAccount() @@ -104,4 +97,12 @@ class AuthRemoteDataStore( override suspend fun clearLastOpenedObject() { throw UnsupportedOperationException() } + + override suspend fun getNetworkMode(): NetworkModeConfig { + throw UnsupportedOperationException() + } + + override suspend fun setNetworkMode(modeConfig: NetworkModeConfig) { + throw UnsupportedOperationException() + } } \ No newline at end of file diff --git a/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt b/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt index 69c455f50e..997dac3934 100644 --- a/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt +++ b/data/src/test/java/com/anytypeio/anytype/data/AuthDataRepositoryTest.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.data +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.StubAccount import com.anytypeio.anytype.core_models.StubAccountSetup import com.anytypeio.anytype.core_models.StubFeatureConfig @@ -68,22 +69,30 @@ class AuthDataRepositoryTest { val features = StubFeatureConfig() authRemote.stub { - onBlocking { selectAccount(id = id, path = path) } doReturn StubAccountSetup( + val command = Command.AccountSelect( + id = id, + path = path + ) + onBlocking { selectAccount(command) } doReturn StubAccountSetup( account = account, features = features ) } repo.selectAccount( - id = id, - path = path + command = Command.AccountSelect( + id = id, + path = path + ) ) verifyNoInteractions(authCache) verify(authRemote, times(1)).selectAccount( - id = id, - path = path + Command.AccountSelect( + id = id, + path = path + ) ) verifyNoMoreInteractions(authRemote) @@ -102,26 +111,31 @@ class AuthDataRepositoryTest { authRemote.stub { onBlocking { - createAccount( + val command = Command.AccountCreate( name = name, avatarPath = path, - iconGradientValue = icon + icon = icon ) + createAccount(command) } doReturn setup } repo.createAccount( - name = name, - avatarPath = path, - icon = icon + Command.AccountCreate( + name = name, + avatarPath = path, + icon = icon + ) ) verifyNoInteractions(authCache) verify(authRemote, times(1)).createAccount( - name = name, - avatarPath = path, - iconGradientValue = icon + Command.AccountCreate( + name = name, + avatarPath = path, + icon = icon + ) ) verifyNoMoreInteractions(authRemote) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CreateAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CreateAccount.kt index be6acc57a4..7b5c885c13 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CreateAccount.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/CreateAccount.kt @@ -1,8 +1,10 @@ package com.anytypeio.anytype.domain.auth.interactor import com.anytypeio.anytype.core_models.Account +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.domain.auth.repo.AuthRepository -import com.anytypeio.anytype.domain.base.BaseUseCase +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.platform.MetricsProvider @@ -11,26 +13,34 @@ import com.anytypeio.anytype.domain.platform.MetricsProvider */ open class CreateAccount( private val repository: AuthRepository, + // TODO rename config storage private val configStorage: ConfigStorage, - private val metricsProvider: MetricsProvider -) : BaseUseCase() { + private val metricsProvider: MetricsProvider, + dispatcher: AppCoroutineDispatchers +) : ResultInteractor(dispatcher.io) { - override suspend fun run(params: Params) = safe { + override suspend fun doWork(params: Params): Account { repository.setMetrics( version = metricsProvider.getVersion(), platform = metricsProvider.getPlatform() ) - val setup = repository.createAccount( + + val networkMode = repository.getNetworkMode() + + val command = Command.AccountCreate( name = params.name, avatarPath = params.avatarPath, - icon = params.iconGradientValue + icon = params.iconGradientValue, + networkMode = networkMode.networkMode, + networkConfigFilePath = networkMode.storedFilePath ) + val setup = repository.createAccount(command) with(repository) { saveAccount(setup.account) setCurrentAccount(setup.account.id) } configStorage.set(setup.config) - setup.account + return setup.account } /** diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt index 113d3aed82..6c215956bd 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/LaunchAccount.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.domain.auth.interactor +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.BaseUseCase import com.anytypeio.anytype.domain.base.Either @@ -32,10 +33,16 @@ class LaunchAccount @Inject constructor( version = metricsProvider.getVersion(), platform = metricsProvider.getPlatform() ) - repository.selectAccount( + + val networkMode = repository.getNetworkMode() + + val command = Command.AccountSelect( id = repository.getCurrentAccountId(), - path = pathProvider.providePath() - ).let { setup -> + path = pathProvider.providePath(), + networkMode = networkMode.networkMode, + networkConfigFilePath = networkMode.storedFilePath + ) + repository.selectAccount(command).let { setup -> repository.updateAccount(setup.account) featuresConfigProvider.set( enableDataView = setup.features.enableDataView ?: false, diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/ResumeAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/ResumeAccount.kt index eeb5d2745a..5c88d49ca4 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/ResumeAccount.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/ResumeAccount.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.domain.auth.interactor +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.BaseUseCase @@ -30,10 +31,16 @@ class ResumeAccount( path = pathProvider.providePath(), mnemonic = repository.getMnemonic() ) - repository.selectAccount( + + val networkMode = repository.getNetworkMode() + + val command = Command.AccountSelect( id = repository.getCurrentAccountId(), - path = pathProvider.providePath() - ).let { setup -> + path = pathProvider.providePath(), + networkMode = networkMode.networkMode, + networkConfigFilePath = networkMode.storedFilePath + ) + repository.selectAccount(command).let { setup -> featuresConfigProvider.set( enableDataView = setup.features.enableDataView ?: false, enableDebug = setup.features.enableDebug ?: false, diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/SelectAccount.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/SelectAccount.kt index 8b2d438692..39949c0c20 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/SelectAccount.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/interactor/SelectAccount.kt @@ -1,6 +1,7 @@ package com.anytypeio.anytype.domain.auth.interactor import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.domain.auth.repo.AuthRepository import com.anytypeio.anytype.domain.base.BaseUseCase @@ -24,10 +25,16 @@ class SelectAccount @Inject constructor( version = metricsProvider.getVersion(), platform = metricsProvider.getPlatform() ) - val setup = repository.selectAccount( + + val networkMode = repository.getNetworkMode() + + val command = Command.AccountSelect( id = params.id, - path = params.path + path = params.path, + networkMode = networkMode.networkMode, + networkConfigFilePath = networkMode.storedFilePath ) + val setup = repository.selectAccount(command) with(repository) { saveAccount(setup.account) setCurrentAccount(setup.account.id) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt index b011e3ebcf..975b50130a 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/auth/repo/AuthRepository.kt @@ -3,7 +3,9 @@ package com.anytypeio.anytype.domain.auth.repo import com.anytypeio.anytype.core_models.Account import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.domain.auth.model.Wallet import kotlinx.coroutines.flow.Flow @@ -16,13 +18,8 @@ interface AuthRepository { * @param id user account id * @param path wallet repository path */ - suspend fun selectAccount(id: String, path: String): AccountSetup - - suspend fun createAccount( - name: String, - avatarPath: String?, - icon: Int - ): AccountSetup + suspend fun selectAccount(command: Command.AccountSelect): AccountSetup + suspend fun createAccount(command: Command.AccountCreate): AccountSetup suspend fun deleteAccount() : AccountStatus suspend fun restoreAccount() : AccountStatus @@ -64,4 +61,7 @@ interface AuthRepository { suspend fun saveLastOpenedObjectId(id: Id) suspend fun getLastOpenedObjectId() : Id? suspend fun clearLastOpenedObject() + + suspend fun getNetworkMode(): NetworkModeConfig + suspend fun setNetworkMode(modeConfig: NetworkModeConfig) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/networkmode/GetNetworkMode.kt b/domain/src/main/java/com/anytypeio/anytype/domain/networkmode/GetNetworkMode.kt new file mode 100644 index 0000000000..f1d62708b7 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/networkmode/GetNetworkMode.kt @@ -0,0 +1,17 @@ +package com.anytypeio.anytype.domain.networkmode + +import com.anytypeio.anytype.core_models.NetworkModeConfig +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import javax.inject.Inject + +class GetNetworkMode @Inject constructor( + private val repository: AuthRepository, + dispatchers: AppCoroutineDispatchers, +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Unit): NetworkModeConfig { + return repository.getNetworkMode() + } +} \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/networkmode/SetNetworkMode.kt b/domain/src/main/java/com/anytypeio/anytype/domain/networkmode/SetNetworkMode.kt new file mode 100644 index 0000000000..16d4243257 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/networkmode/SetNetworkMode.kt @@ -0,0 +1,19 @@ +package com.anytypeio.anytype.domain.networkmode + +import com.anytypeio.anytype.core_models.NetworkModeConfig +import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.base.ResultInteractor +import javax.inject.Inject + +class SetNetworkMode @Inject constructor( + private val repository: AuthRepository, + dispatchers: AppCoroutineDispatchers, +) : ResultInteractor(dispatchers.io) { + + override suspend fun doWork(params: Params) { + repository.setNetworkMode(params.networkModeConfig) + } + + data class Params(val networkModeConfig: NetworkModeConfig) +} \ No newline at end of file diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt index 1c5301d112..0c863f15bb 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/auth/CreateAccountTest.kt @@ -1,12 +1,16 @@ package com.anytypeio.anytype.domain.auth +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.CoroutineTestRule +import com.anytypeio.anytype.core_models.NetworkMode +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.core_models.StubAccountSetup import com.anytypeio.anytype.domain.auth.interactor.CreateAccount import com.anytypeio.anytype.domain.auth.repo.AuthRepository +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers +import com.anytypeio.anytype.domain.common.DefaultCoroutineTestRule import com.anytypeio.anytype.domain.config.ConfigStorage import com.anytypeio.anytype.domain.platform.MetricsProvider -import com.anytypeio.anytype.domain.workspace.WorkspaceManager import com.anytypeio.anytype.test_utils.MockDataFactory import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -23,9 +27,8 @@ import org.mockito.kotlin.verifyNoMoreInteractions class CreateAccountTest { - @ExperimentalCoroutinesApi @get:Rule - var rule = CoroutineTestRule() + val rule = DefaultCoroutineTestRule() @Mock lateinit var repo: AuthRepository @@ -33,8 +36,7 @@ class CreateAccountTest { @Mock lateinit var configStorage: ConfigStorage - @Mock - lateinit var workspaceManager: WorkspaceManager + lateinit var dispatchers: AppCoroutineDispatchers @Mock lateinit var metricsProvider: MetricsProvider @@ -42,13 +44,20 @@ class CreateAccountTest { private lateinit var createAccount: CreateAccount + @OptIn(ExperimentalCoroutinesApi::class) @Before fun setup() { MockitoAnnotations.initMocks(this) + dispatchers = AppCoroutineDispatchers( + io = rule.dispatcher, + computation = rule.dispatcher, + main = rule.dispatcher + ) createAccount = CreateAccount( repository = repo, configStorage = configStorage, - metricsProvider = metricsProvider + metricsProvider = metricsProvider, + dispatcher = dispatchers ) } @@ -66,7 +75,21 @@ class CreateAccountTest { ) repo.stub { - onBlocking { createAccount(name, path, icon) } doReturn setup + onBlocking { + getNetworkMode() + } doReturn NetworkModeConfig( + networkMode = NetworkMode.DEFAULT + ) + } + + repo.stub { + val command = Command.AccountCreate( + name = name, + avatarPath = path, + icon = icon, + networkMode = NetworkMode.DEFAULT + ) + onBlocking { createAccount(command) } doReturn setup } val version = MockDataFactory.randomString() @@ -76,7 +99,14 @@ class CreateAccountTest { createAccount.run(param) - verify(repo, times(1)).createAccount(name, path, icon) + val command = Command.AccountCreate( + name = name, + avatarPath = path, + icon = icon, + networkMode = NetworkMode.DEFAULT + ) + verify(repo, times(1)).getNetworkMode() + verify(repo, times(1)).createAccount(command) verify(repo, times(1)).saveAccount(setup.account) verify(repo, times(1)).setCurrentAccount(setup.account.id) verify(repo, times(1)).setMetrics( diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt b/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt index a51f6d2500..b1201fab35 100644 --- a/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt +++ b/domain/src/test/java/com/anytypeio/anytype/domain/auth/StartAccountTest.kt @@ -3,10 +3,11 @@ package com.anytypeio.anytype.domain.auth import com.anytypeio.anytype.core_models.Account import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.core_models.CoroutineTestRule import com.anytypeio.anytype.core_models.FeaturesConfig -import com.anytypeio.anytype.core_models.StubAccount -import com.anytypeio.anytype.core_models.StubAccountSetup +import com.anytypeio.anytype.core_models.NetworkMode +import com.anytypeio.anytype.core_models.NetworkModeConfig import com.anytypeio.anytype.core_models.StubConfig import com.anytypeio.anytype.domain.auth.interactor.SelectAccount import com.anytypeio.anytype.domain.auth.repo.AuthRepository @@ -19,7 +20,6 @@ import com.anytypeio.anytype.test_utils.MockDataFactory import kotlin.test.assertTrue import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -29,7 +29,6 @@ import org.mockito.kotlin.doReturn import org.mockito.kotlin.stub import org.mockito.kotlin.times import org.mockito.kotlin.verify -import org.mockito.kotlin.verifyBlocking import org.mockito.kotlin.verifyNoMoreInteractions class StartAccountTest { @@ -97,10 +96,20 @@ class StartAccountTest { repo.stub { onBlocking { - selectAccount( + getNetworkMode() + } doReturn NetworkModeConfig( + networkMode = NetworkMode.DEFAULT + ) + } + + repo.stub { + onBlocking { + val command = Command.AccountSelect( id = id, - path = path + path = path, + networkMode = NetworkMode.DEFAULT ) + selectAccount(command) } doReturn AccountSetup( account = account, features = featuresConfig, @@ -116,11 +125,15 @@ class StartAccountTest { selectAccount.run(params) - verify(repo, times(1)).selectAccount( + val command = Command.AccountSelect( id = id, - path = path + path = path, + networkMode = NetworkMode.DEFAULT ) + verify(repo, times(1)).getNetworkMode() + verify(repo, times(1)).selectAccount(command) + verify(repo, times(1)).saveAccount(account) verify(repo, times(1)).setCurrentAccount(account.id) @@ -159,10 +172,20 @@ class StartAccountTest { repo.stub { onBlocking { - selectAccount( + getNetworkMode() + } doReturn NetworkModeConfig( + networkMode = NetworkMode.DEFAULT + ) + } + + repo.stub { + onBlocking { + val command = Command.AccountSelect( id = id, - path = path + path = path, + networkMode = NetworkMode.DEFAULT ) + selectAccount(command) } doReturn AccountSetup( account = account, features = featuresConfig, @@ -202,10 +225,20 @@ class StartAccountTest { repo.stub { onBlocking { - selectAccount( + getNetworkMode() + } doReturn NetworkModeConfig( + networkMode = NetworkMode.DEFAULT + ) + } + + repo.stub { + onBlocking { + val command = Command.AccountSelect( id = id, - path = path + path = path, + networkMode = NetworkMode.DEFAULT ) + selectAccount(command) } doReturn AccountSetup( account = account, features = featuresConfig, @@ -252,10 +285,146 @@ class StartAccountTest { repo.stub { onBlocking { - selectAccount( + getNetworkMode() + } doReturn NetworkModeConfig( + networkMode = NetworkMode.DEFAULT + ) + } + + repo.stub { + onBlocking { + val command = Command.AccountSelect( id = id, - path = path + path = path, + networkMode = NetworkMode.DEFAULT ) + selectAccount(command) + } doReturn AccountSetup( + account = account, + features = featuresConfig, + status = AccountStatus.Active, + config = config + ) + } + + val result = selectAccount.run(params) + + verify(featuresConfigProvider, times(1)).set( + enableDataView = true, + enableDebug = false, + enableChannelSwitch = true, + enableSpaces = false + ) + + assertTrue { result == Either.Right(Pair(config.analytics, AccountStatus.Active)) } + } + + @Test + fun `should send local mode config on account select`() = runBlocking { + + val id = MockDataFactory.randomString() + val path = MockDataFactory.randomString() + + val params = SelectAccount.Params( + id = id, + path = path + ) + + val account = Account( + id = id, + name = MockDataFactory.randomString(), + avatar = null, + color = null + ) + + val featuresConfig = FeaturesConfig( + enableDataView = true, + enableDebug = false, + enablePrereleaseChannel = true + ) + + repo.stub { + onBlocking { + getNetworkMode() + } doReturn NetworkModeConfig( + networkMode = NetworkMode.LOCAL + ) + } + + repo.stub { + onBlocking { + val command = Command.AccountSelect( + id = id, + path = path, + networkMode = NetworkMode.LOCAL + ) + selectAccount(command) + } doReturn AccountSetup( + account = account, + features = featuresConfig, + status = AccountStatus.Active, + config = config + ) + } + + val result = selectAccount.run(params) + + verify(featuresConfigProvider, times(1)).set( + enableDataView = true, + enableDebug = false, + enableChannelSwitch = true, + enableSpaces = false + ) + + assertTrue { result == Either.Right(Pair(config.analytics, AccountStatus.Active)) } + } + + @Test + fun `should send custom mode config with path on account select`() = runBlocking { + + val id = MockDataFactory.randomString() + val path = MockDataFactory.randomString() + + val params = SelectAccount.Params( + id = id, + path = path + ) + + val account = Account( + id = id, + name = MockDataFactory.randomString(), + avatar = null, + color = null + ) + + val featuresConfig = FeaturesConfig( + enableDataView = true, + enableDebug = false, + enablePrereleaseChannel = true + ) + + val storedFilePath = MockDataFactory.randomString() + val userPath = MockDataFactory.randomString() + + repo.stub { + onBlocking { + getNetworkMode() + } doReturn NetworkModeConfig( + networkMode = NetworkMode.CUSTOM, + storedFilePath = storedFilePath, + userFilePath = userPath + ) + } + + repo.stub { + onBlocking { + val command = Command.AccountSelect( + id = id, + path = path, + networkMode = NetworkMode.CUSTOM, + networkConfigFilePath = storedFilePath + ) + selectAccount(command) } doReturn AccountSetup( account = account, features = featuresConfig, diff --git a/domain/src/test/java/com/anytypeio/anytype/domain/common/DefaultCoroutineTestRule.kt b/domain/src/test/java/com/anytypeio/anytype/domain/common/DefaultCoroutineTestRule.kt new file mode 100644 index 0000000000..b0f2391838 --- /dev/null +++ b/domain/src/test/java/com/anytypeio/anytype/domain/common/DefaultCoroutineTestRule.kt @@ -0,0 +1,29 @@ +package com.anytypeio.anytype.domain.common + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + +@OptIn(ExperimentalCoroutinesApi::class) +class DefaultCoroutineTestRule() : TestWatcher() { + + val dispatcher = StandardTestDispatcher(name = "Default test dispatcher") + + override fun starting(description: Description) { + super.starting(description) + Dispatchers.setMain(dispatcher) + } + + override fun finished(description: Description) { + super.finished(description) + Dispatchers.resetMain() + } + + fun advanceTime(millis: Long = 100L) { + dispatcher.scheduler.advanceTimeBy(millis) + } +} \ No newline at end of file diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index dab963e500..a73caafc4c 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -826,6 +826,21 @@ debug_mode trouble_mode Settings + Network mode + Network Configuration File + Choose file + + + Default + Local-only Mode + Custom + + + + default + local + custom + linked to moved to diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt index 5265b4e275..ef7131b864 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/auth/AuthMiddleware.kt @@ -2,6 +2,7 @@ package com.anytypeio.anytype.middleware.auth import com.anytypeio.anytype.core_models.AccountSetup import com.anytypeio.anytype.core_models.AccountStatus +import com.anytypeio.anytype.core_models.Command import com.anytypeio.anytype.data.auth.model.WalletEntity import com.anytypeio.anytype.data.auth.repo.AuthRemote import com.anytypeio.anytype.middleware.EventProxy @@ -20,18 +21,12 @@ class AuthMiddleware( ) : AuthRemote { override suspend fun selectAccount( - id: String, path: String - ): AccountSetup = middleware.accountSelect(id, path) + command: Command.AccountSelect + ): AccountSetup = middleware.accountSelect(command) override suspend fun createAccount( - name: String, - avatarPath: String?, - iconGradientValue: Int, - ) : AccountSetup = middleware.accountCreate( - name = name, - path = avatarPath, - iconGradientValue = iconGradientValue - ) + command: Command.AccountCreate + ) : AccountSetup = middleware.accountCreate(command) override suspend fun deleteAccount(): AccountStatus = middleware.accountDelete() override suspend fun restoreAccount(): AccountStatus = middleware.accountRestore() diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index d135f8c6fa..05e985aef0 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -35,6 +35,7 @@ import com.anytypeio.anytype.middleware.BuildConfig import com.anytypeio.anytype.middleware.auth.toAccountSetup import com.anytypeio.anytype.middleware.const.Constants import com.anytypeio.anytype.middleware.mappers.MDVFilter +import com.anytypeio.anytype.middleware.mappers.MNetworkMode import com.anytypeio.anytype.middleware.mappers.MRelationFormat import com.anytypeio.anytype.middleware.mappers.config import com.anytypeio.anytype.middleware.mappers.core @@ -59,15 +60,15 @@ class Middleware @Inject constructor( ) { @Throws(Exception::class) - fun accountCreate( - name: String, - path: String?, - iconGradientValue: Int - ): AccountSetup { + fun accountCreate(command: Command.AccountCreate): AccountSetup { + val request = Rpc.Account.Create.Request( - name = name, - avatarLocalPath = path, - icon = iconGradientValue.toLong() + name = command.name, + avatarLocalPath = command.avatarPath, + icon = command.icon.toLong(), + networkMode = command.networkMode?.toMiddlewareModel() ?: MNetworkMode.DefaultConfig, + networkCustomConfigFilePath = command.networkConfigFilePath.orEmpty() + ) if (BuildConfig.DEBUG) logRequest(request) val response = service.accountCreate(request) @@ -75,6 +76,25 @@ class Middleware @Inject constructor( return response.toAccountSetup() } + @Throws(Exception::class) + fun accountSelect(command: Command.AccountSelect): AccountSetup { + + val networkMode = command.networkMode?.toMiddlewareModel() ?: MNetworkMode.DefaultConfig + val networkCustomConfigFilePath = if (networkMode == MNetworkMode.CustomConfig) { + command.networkConfigFilePath.orEmpty() + } else "" + val request = Rpc.Account.Select.Request( + id = command.id, + rootPath = command.path, + networkMode = networkMode, + networkCustomConfigFilePath = networkCustomConfigFilePath + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.accountSelect(request) + if (BuildConfig.DEBUG) logResponse(response) + return response.toAccountSetup() + } + @Throws(Exception::class) fun accountDelete(): AccountStatus { val request = Rpc.Account.Delete.Request() @@ -105,18 +125,6 @@ class Middleware @Inject constructor( return status.core() } - @Throws(Exception::class) - fun accountSelect(id: String, path: String): AccountSetup { - val request = Rpc.Account.Select.Request( - id = id, - rootPath = path - ) - if (BuildConfig.DEBUG) logRequest(request) - val response = service.accountSelect(request) - if (BuildConfig.DEBUG) logResponse(response) - return response.toAccountSetup() - } - @Throws(Exception::class) fun accountStop(clearLocalRepositoryData: Boolean) { val request: Rpc.Account.Stop.Request = Rpc.Account.Stop.Request( diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt index ca5aab2641..43da9bb30d 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/Alias.kt @@ -63,3 +63,4 @@ typealias MDVRestriction = anytype.model.Restrictions.DataviewRestriction typealias MWidget = anytype.model.Block.Content.Widget typealias MWidgetLayout = anytype.model.Block.Content.Widget.Layout +typealias MNetworkMode = anytype.Rpc.Account.NetworkMode diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt index 987ce4db83..9adfb95f43 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/mappers/ToMiddlewareModelMappers.kt @@ -6,6 +6,7 @@ import anytype.model.RelationFormat import com.anytypeio.anytype.core_models.Block import com.anytypeio.anytype.core_models.BlockSplitMode import com.anytypeio.anytype.core_models.InternalFlags +import com.anytypeio.anytype.core_models.NetworkMode import com.anytypeio.anytype.core_models.ObjectType import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_models.Relation @@ -497,4 +498,10 @@ fun Block.Content.Widget.Layout.mw() : MWidgetLayout = when(this) { Block.Content.Widget.Layout.LINK -> MWidgetLayout.Link Block.Content.Widget.Layout.LIST -> MWidgetLayout.List Block.Content.Widget.Layout.COMPACT_LIST -> MWidgetLayout.CompactList +} + +fun NetworkMode.toMiddlewareModel(): MNetworkMode = when (this) { + NetworkMode.DEFAULT -> MNetworkMode.DefaultConfig + NetworkMode.LOCAL -> MNetworkMode.LocalOnly + NetworkMode.CUSTOM -> MNetworkMode.CustomConfig } \ No newline at end of file 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 new file mode 100644 index 0000000000..97666eb8e3 --- /dev/null +++ b/persistence/src/main/java/com/anytypeio/anytype/persistence/networkmode/NetworkModeProvider.kt @@ -0,0 +1,70 @@ +package com.anytypeio.anytype.persistence.networkmode + +import android.content.SharedPreferences +import com.anytypeio.anytype.core_models.NetworkMode +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.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 + +interface NetworkModeProvider { + fun set(networkModeConfig: NetworkModeConfig) + fun get(): NetworkModeConfig +} + +class DefaultNetworkModeProvider(private val sharedPreferences: SharedPreferences) : + NetworkModeProvider { + + override fun set(networkModeConfig: NetworkModeConfig) { + val (userFilePath, storedFilePath) = if (networkModeConfig.networkMode == NetworkMode.CUSTOM) { + networkModeConfig.userFilePath to networkModeConfig.storedFilePath + } else { + null to null + } + + val modeValue= when (networkModeConfig.networkMode) { + NetworkMode.DEFAULT -> NETWORK_MODE_DEFAULT + NetworkMode.LOCAL -> NETWORK_MODE_LOCAL + NetworkMode.CUSTOM -> NETWORK_MODE_CUSTOM + } + + sharedPreferences.edit().apply { + putString(NETWORK_MODE_PREF, modeValue) + putString(NETWORK_MODE_USER_FILE_PATH_PREF, userFilePath) + putString(NETWORK_MODE_APP_FILE_PATH_PREF, storedFilePath) + apply() + } + } + + override fun get(): NetworkModeConfig { + val networkMode = + when (sharedPreferences.getString(NETWORK_MODE_PREF, NETWORK_MODE_DEFAULT)) { + NETWORK_MODE_DEFAULT -> NetworkMode.DEFAULT + NETWORK_MODE_LOCAL -> NetworkMode.LOCAL + NETWORK_MODE_CUSTOM -> NetworkMode.CUSTOM + else -> NetworkMode.DEFAULT + } + return if (networkMode == NetworkMode.CUSTOM) { + val userFilePath = sharedPreferences.getString( + NETWORK_MODE_USER_FILE_PATH_PREF, null + ) + val storedFilePath = sharedPreferences.getString( + NETWORK_MODE_APP_FILE_PATH_PREF, null + ) + NetworkModeConfig(networkMode, userFilePath, storedFilePath) + } else { + NetworkModeConfig(networkMode, null, null) + } + } + + object NetworkModeConstants { + const val NETWORK_MODE_PREF = "pref.network_mode" + const val NETWORK_MODE_APP_FILE_PATH_PREF = "pref.network_config_file_path" + const val NETWORK_MODE_USER_FILE_PATH_PREF = "pref.network_mode_user_config_file_path" + + const val NAMED_NETWORK_MODE_PREFS = "network_mode" + } +} \ No newline at end of file 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 727cd4ddb0..637a644e9d 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 @@ -1,16 +1,19 @@ package com.anytypeio.anytype.persistence.repo 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.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, private val defaultPrefs: SharedPreferences, - private val encryptedPrefs: SharedPreferences + private val encryptedPrefs: SharedPreferences, + private val networkModeProvider: NetworkModeProvider ) : AuthCache { override suspend fun saveAccount(account: AccountEntity) { @@ -101,6 +104,11 @@ class DefaultAuthCache( encryptedPrefs.edit().remove(LAST_OPENED_OBJECT_KEY).apply() } + override suspend fun getNetworkMode(): NetworkModeConfig = networkModeProvider.get() + override suspend fun setNetworkMode(modeConfig: NetworkModeConfig) { + networkModeProvider.set(modeConfig) + } + companion object { const val MNEMONIC_KEY = "mnemonic" const val LAST_OPENED_OBJECT_KEY = "last_opened_object" 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 c4038deeda..16496bc819 100644 --- a/persistence/src/test/java/com/anytypeio/anytype/persistence/DefaultAuthCacheTest.kt +++ b/persistence/src/test/java/com/anytypeio/anytype/persistence/DefaultAuthCacheTest.kt @@ -5,6 +5,8 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.room.Room import androidx.test.platform.app.InstrumentationRegistry 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 @@ -41,6 +43,8 @@ class DefaultAuthCacheTest { Context.MODE_PRIVATE ) + lateinit var networkModeProvider: NetworkModeProvider + @After fun after() { database.close() @@ -51,10 +55,15 @@ class DefaultAuthCacheTest { val givenMnemonic = MockDataFactory.randomString() + val networkModeProvider = DefaultNetworkModeProvider( + sharedPreferences = defaultPrefs + ) + val cache = DefaultAuthCache( db = database, encryptedPrefs = encryptedPrefs, - defaultPrefs = defaultPrefs + defaultPrefs = defaultPrefs, + networkModeProvider = networkModeProvider ) cache.saveMnemonic(mnemonic = givenMnemonic) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index f507f4ce73..faded2737c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -238,7 +238,7 @@ import com.anytypeio.anytype.presentation.templates.ObjectTypeTemplatesContainer import com.anytypeio.anytype.presentation.util.CopyFileStatus import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher -import com.anytypeio.anytype.presentation.util.OnCopyFileToCacheAction +import com.anytypeio.anytype.presentation.util.CopyFileToCacheStatus import java.util.* import java.util.regex.Pattern import kotlinx.coroutines.Job @@ -6221,14 +6221,14 @@ class EditorViewModel( copyFileToCache.cancel() } - private val copyFileListener = object : OnCopyFileToCacheAction { + private val copyFileListener = object : CopyFileToCacheStatus { override fun onCopyFileStart() { viewModelScope.launch { copyFileStatus.emit(CopyFileStatus.Started) } } - override fun onCopyFileResult(result: String?) { + override fun onCopyFileResult(result: String?, fileName: String?) { viewModelScope.launch { copyFileStatus.emit(CopyFileStatus.Completed(result)) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt index 5deeac8ea8..ea135c4a13 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingSetProfileNameViewModel.kt @@ -85,36 +85,33 @@ class OnboardingSetProfileNameViewModel @Inject constructor( private fun proceedWithCreatingAccount(name: String) { val startTime = System.currentTimeMillis() - createAccount.invoke( - scope = viewModelScope, - params = CreateAccount.Params( - name = name, - avatarPath = null, - iconGradientValue = spaceGradientProvider.randomId() - ) - ) { result -> - result.either( - fnL = { error -> + val params = CreateAccount.Params( + name = name, + avatarPath = null, + iconGradientValue = spaceGradientProvider.randomId() + ) + viewModelScope.launch { + createAccount.async(params = params).fold( + onFailure = { error -> Timber.d("Error while creating account: ${error.message ?: "Unknown error"}").also { - when(error) { - CreateAccountException.NetworkError -> { - sendToast( - "Failed to create your account due to a network error: ${error.message ?: "Unknown error"}" - ) - } - CreateAccountException.OfflineDevice -> { - sendToast("Your device seems to be offline. Please, check your connection and try again.") - } - else -> { - sendToast("Error while creating an account: ${error.message ?: "Unknown error"}") + when (error) { + CreateAccountException.NetworkError -> { + sendToast( + "Failed to create your account due to a network error: ${error.message ?: "Unknown error"}" + ) + } + CreateAccountException.OfflineDevice -> { + sendToast("Your device seems to be offline. Please, check your connection and try again.") + } + else -> { + sendToast("Error: ${error.message ?: "Unknown error"}") + } } } - } + state.value = ScreenState.Idle }, - fnR = { - viewModelScope.launch { - analytics.sendEvent(eventName = EventsDictionary.createSpace) - } + onSuccess = { + analytics.sendEvent(eventName = EventsDictionary.createSpace) createAccountAnalytics(startTime) val config = configStorage.getOrNull() if (config != null) { diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingVoidViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingVoidViewModel.kt index e6dd29bfec..15722d36f4 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingVoidViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/onboarding/signup/OnboardingVoidViewModel.kt @@ -78,18 +78,17 @@ class OnboardingVoidViewModel @Inject constructor( } } + @Deprecated("To be deleted") private fun proceedWithCreatingAccount() { val startTime = System.currentTimeMillis() - createAccount.invoke( - scope = viewModelScope, - params = CreateAccount.Params( - name = "", - avatarPath = null, - iconGradientValue = spaceGradientProvider.randomId() - ) - ) { result -> - result.either( - fnL = { error -> + val params = CreateAccount.Params( + name = "", + avatarPath = null, + iconGradientValue = spaceGradientProvider.randomId() + ) + viewModelScope.launch { + createAccount.async(params = params).fold( + onFailure = { error -> Timber.d("Error while creating account: ${error.message ?: "Unknown error"}").also { when(error) { CreateAccountException.NetworkError -> { @@ -101,12 +100,13 @@ class OnboardingVoidViewModel @Inject constructor( sendToast("Your device seems to be offline. Please, check your connection and try again.") } else -> { - sendToast("Error while creating an account: ${error.message ?: "Unknown error"}") + sendToast("Error: ${error.message ?: "Unknown error"}") } } } + state.value = ScreenState.Idle }, - fnR = { + onSuccess = { createAccountAnalytics(startTime) val config = configStorage.getOrNull() if (config != null) { @@ -115,7 +115,8 @@ class OnboardingVoidViewModel @Inject constructor( objectTypesSubscriptionManager.onStart() proceedWithSettingUpMobileUseCase(config.space) } - } + }, + ) } } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt index d62bc0b031..ad3f07d941 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/sets/RelationValueBaseViewModel.kt @@ -32,7 +32,7 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvide import com.anytypeio.anytype.presentation.util.CopyFileStatus import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher -import com.anytypeio.anytype.presentation.util.OnCopyFileToCacheAction +import com.anytypeio.anytype.presentation.util.CopyFileToCacheStatus import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -569,14 +569,14 @@ abstract class RelationValueBaseViewModel( copyFileToCache.cancel() } - private val copyFileListener = object : OnCopyFileToCacheAction { + private val copyFileListener = object : CopyFileToCacheStatus { override fun onCopyFileStart() { viewModelScope.launch { copyFileStatus.emit(CopyFileStatus.Started) } } - override fun onCopyFileResult(result: String?) { + override fun onCopyFileResult(result: String?, fileName: String?) { viewModelScope.launch { copyFileStatus.emit(CopyFileStatus.Completed(result)) } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/PreferencesViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/PreferencesViewModel.kt new file mode 100644 index 0000000000..38c672b597 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/settings/PreferencesViewModel.kt @@ -0,0 +1,120 @@ +package com.anytypeio.anytype.presentation.settings + +import android.net.Uri +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.anytypeio.anytype.core_models.Id +import com.anytypeio.anytype.core_models.NetworkMode +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_LOCAL +import com.anytypeio.anytype.domain.base.fold +import com.anytypeio.anytype.domain.networkmode.GetNetworkMode +import com.anytypeio.anytype.domain.networkmode.SetNetworkMode +import com.anytypeio.anytype.presentation.editor.picker.PickerListener +import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory +import com.anytypeio.anytype.presentation.util.CopyFileToCacheStatus +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import timber.log.Timber + +class PreferencesViewModel( + private val copyFileToCache: CopyFileToCacheDirectory, + private val getNetworkMode: GetNetworkMode, + private val setNetworkMode: SetNetworkMode +) : ViewModel(), PickerListener { + + val networkModeState = MutableStateFlow(NetworkModeConfig(NetworkMode.DEFAULT, "", "")) + + fun onStart() { + Timber.d("onStart") + viewModelScope.launch { + getNetworkMode.async(Unit).fold( + onSuccess = { + networkModeState.value = it + Timber.d("Successfully get network mode on Start: $it") + }, + onFailure = { + Timber.e(it, "Failed to get network mode") + } + ) + } + } + + fun proceedWithNetworkMode(mode: String?) { + viewModelScope.launch { + val config = when (mode) { + NETWORK_MODE_LOCAL -> NetworkModeConfig(NetworkMode.LOCAL) + NETWORK_MODE_CUSTOM -> NetworkModeConfig(NetworkMode.CUSTOM) + else -> NetworkModeConfig() + } + networkModeState.value = config + setNetworkMode.async(SetNetworkMode.Params(config)).fold( + onSuccess = { Timber.d("Successfully update network mode with config:$config") }, + onFailure = { Timber.e(it, "Failed to set network mode") } + ) + } + } + + fun proceedWithConfigFiles(userFilePath: String?, storedFilePath: String?) { + viewModelScope.launch { + val config = NetworkModeConfig( + networkMode = NetworkMode.CUSTOM, + userFilePath = userFilePath, + storedFilePath = storedFilePath + ) + val params = SetNetworkMode.Params(config) + setNetworkMode.async(params).fold( + onSuccess = { + networkModeState.value = config + Timber.d("Successfully update network mode with config:$config") + }, + onFailure = { Timber.e(it, "Failed to set network mode") } + ) + } + } + + override fun onStartCopyFileToCacheDir(uri: Uri) { + Timber.d("onStartCopyFileToCacheDir: $uri") + copyFileToCache.execute( + uri = uri, + scope = viewModelScope, + listener = copyFileListener + ) + } + + override fun onCancelCopyFileToCacheDir() { + copyFileToCache.cancel() + } + + override fun onPickedDocImageFromDevice(ctx: Id, path: String) {} + override fun onProceedWithFilePath(filePath: String?) {} + + private val copyFileListener = object : CopyFileToCacheStatus { + override fun onCopyFileStart() {} + override fun onCopyFileError(msg: String) {} + + override fun onCopyFileResult(result: String?, fileName: String?) { + proceedWithConfigFiles( + userFilePath = fileName, + storedFilePath = result + ) + } + } + + class Factory( + private val copyFileToCacheDirectory: CopyFileToCacheDirectory, + private val getNetworkMode: GetNetworkMode, + private val setNetworkMode: SetNetworkMode + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: Class + ): T = PreferencesViewModel( + copyFileToCache = copyFileToCacheDirectory, + getNetworkMode = getNetworkMode, + setNetworkMode = setNetworkMode + ) as T + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt index a272707132..45e60a8b2c 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/CopyFileToCache.kt @@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.util import android.content.Context import android.net.Uri import android.provider.OpenableColumns +import com.anytypeio.anytype.core_utils.ext.msg import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -14,17 +15,49 @@ import java.io.File import java.io.FileOutputStream import java.lang.ref.WeakReference +/** + * Interface defining the contract for copying files to a cache directory. + */ interface CopyFileToCacheDirectory { - fun execute(uri: Uri, scope: CoroutineScope, listener: OnCopyFileToCacheAction) + /** + * Executes the file copying operation for the given [uri]. + * + * @param uri The URI of the file to be copied. + * @param scope The [CoroutineScope] used for the asynchronous operation. + * @param listener The [CopyFileToCacheStatus] listener to handle events during the operation. + */ + fun execute(uri: Uri, scope: CoroutineScope, listener: CopyFileToCacheStatus) + + /** + * Cancels the ongoing file copying operation. + */ fun cancel() + + /** + * Checks if the file copying operation is currently active. + * + * @return `true` if the operation is active, `false` otherwise. + */ fun isActive(): Boolean } +/** + * Listener interface to handle events during the file copying operation. + */ +interface CopyFileToCacheStatus { + fun onCopyFileStart() + fun onCopyFileResult(result: String?, fileName: String? = null) + fun onCopyFileError(msg: String) +} + const val SCHEME_CONTENT = "content" const val CHAR_SLASH = '/' const val TEMPORARY_DIRECTORY_NAME = "TemporaryFiles" +/** + * Represents the status of a file copying operation. + */ sealed class CopyFileStatus { data class Error(val msg: String) : CopyFileStatus() object Started : CopyFileStatus() @@ -42,7 +75,7 @@ class DefaultCopyFileToCacheDirectory(context: Context) : CopyFileToCacheDirecto override fun isActive(): Boolean = job?.isActive == true - override fun execute(uri: Uri, scope: CoroutineScope, listener: OnCopyFileToCacheAction) { + override fun execute(uri: Uri, scope: CoroutineScope, listener: CopyFileToCacheStatus) { getNewPathInCacheDir( uri = uri, scope = scope, @@ -58,7 +91,7 @@ class DefaultCopyFileToCacheDirectory(context: Context) : CopyFileToCacheDirecto private fun getNewPathInCacheDir( uri: Uri, scope: CoroutineScope, - listener: OnCopyFileToCacheAction + listener: CopyFileToCacheStatus ) { var path: String? = null job = scope.launch { @@ -68,7 +101,7 @@ class DefaultCopyFileToCacheDirectory(context: Context) : CopyFileToCacheDirecto } } catch (e: Exception) { Timber.e(e, "Error while getNewPathInCacheDir") - listener.onCopyFileError(e.localizedMessage ?: "Unknown error") + listener.onCopyFileError(e.msg()) } finally { if (scope.isActive) { listener.onCopyFileResult(path) @@ -77,9 +110,9 @@ class DefaultCopyFileToCacheDirectory(context: Context) : CopyFileToCacheDirecto } } - fun copyFileToCacheDir( + private fun copyFileToCacheDir( uri: Uri, - listener: OnCopyFileToCacheAction + listener: CopyFileToCacheStatus ): String? { var newFile: File? = null mContext?.get()?.let { context: Context -> @@ -142,6 +175,123 @@ class DefaultCopyFileToCacheDirectory(context: Context) : CopyFileToCacheDirecto } } +/** + * Network mode-specific implementation of the [CopyFileToCacheDirectory] interface. + * + * @param context The application context. + */ +class NetworkModeCopyFileToCacheDirectory(context: Context) : CopyFileToCacheDirectory { + + private var mContext: WeakReference? = null + private var job: Job? = null + + init { + mContext = WeakReference(context) + } + + override fun isActive(): Boolean = job?.isActive == true + + override fun execute(uri: Uri, scope: CoroutineScope, listener: CopyFileToCacheStatus) { + getNewPathInCacheDir( + uri = uri, + scope = scope, + listener = listener, + ) + } + + override fun cancel() { + job?.cancel() + } + + private fun getNewPathInCacheDir( + uri: Uri, + scope: CoroutineScope, + listener: CopyFileToCacheStatus + ) { + var path: String? = null + var fileName: String? = null + job = scope.launch { + try { + withContext(Dispatchers.IO) { + val pair = copyFileToCacheDir(uri, listener) + path = pair.first + fileName = pair.second + } + } catch (e: Exception) { + Timber.e(e, "Error while getNewPathInCacheDir") + listener.onCopyFileError(e.msg()) + } finally { + if (scope.isActive) { + listener.onCopyFileResult(path, fileName) + } + } + } + } + + private fun copyFileToCacheDir( + uri: Uri, + listener: CopyFileToCacheStatus + ): Pair { + var newFile: File? = null + mContext?.get()?.let { context: Context -> + + val fileName = getFileName(context, uri) + + val cacheDir = context.getExternalCustomNetworkDirTemp() + if (cacheDir != null && !cacheDir.exists()) { + cacheDir.mkdirs() + } + try { + val inputStream = context.contentResolver.openInputStream(uri) + inputStream?.use { input -> + newFile = File(cacheDir?.path + "/" + CONFIG_FILE_NAME); + listener.onCopyFileStart() + Timber.d("Start copy file to cache : ${newFile?.path}") + FileOutputStream(newFile).use { output -> + val buffer = ByteArray(1024) + var read: Int = input.read(buffer) + while (read != -1) { + output.write(buffer, 0, read) + read = input.read(buffer) + } + } + return Pair(newFile?.path, fileName) + } + } catch (e: Exception) { + val deleteResult = newFile?.deleteRecursively() + Timber.d("Get exception while copying file, deleteRecursively success: $deleteResult") + Timber.e(e, "Error while coping file") + } + } + return Pair(null, null) + } + + private fun getFileName(context: Context, uri: Uri): String? { + var result: String? = null + if (uri.scheme == SCHEME_CONTENT) { + context.contentResolver.query( + uri, + null, + null, + null, + null + )?.use { cursor -> + if (cursor.moveToFirst()) { + val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + if (index != -1) { + result = cursor.getString(index) + } + } + } + } + return result + } + + companion object { + const val CONFIG_FILE_NAME = "configCustom.txt" + } +} + /** * Delete the /storage/emulated/0/Android/data/package/files/$TEMPORARY_DIRECTORY_NAME folder. */ @@ -160,8 +310,7 @@ private fun Context.deleteTemporaryFolder() { */ private fun Context.getExternalFilesDirTemp(): File? = getExternalFilesDir(TEMPORARY_DIRECTORY_NAME) -interface OnCopyFileToCacheAction { - fun onCopyFileStart() - fun onCopyFileResult(result: String?) - fun onCopyFileError(msg: String) -} \ No newline at end of file +/** + * Return /storage/emulated/0/Android/data/io.anytype.app/files/networkModeConfig directory + */ +private fun Context.getExternalCustomNetworkDirTemp(): File? = getExternalFilesDir("networkModeConfig") \ No newline at end of file