1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-1602 Self-hosting | Auth (#659)

Co-authored-by: Evgenii Kozlov <ubuphobos@gmail.com>
This commit is contained in:
Konstantin Ivanov 2023-12-09 14:53:19 +01:00 committed by GitHub
parent cf1731b217
commit f0b542261d
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1196 additions and 223 deletions

View file

@ -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<T>(private val builder: () -> T) {

View file

@ -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
}

View file

@ -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

View file

@ -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
)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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")

View file

@ -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<PreferencesViewModel> { 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()
}
}

View file

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:defaultValue="false"
android:key="@string/trouble_mode"
android:title="Troubleshooting mode" />
</PreferenceScreen>

View file

@ -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?

View file

@ -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
)

View file

@ -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"
}

View file

@ -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
}

View file

@ -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) {

View file

@ -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<String> =
private fun getPermissionToRequestForImages(): Array<String> =
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<String> =
private fun getPermissionToRequestForVideos(): Array<String> =
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<String> =
private fun getPermissionToRequestForFiles(): Array<String> =
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
fun getPermissionToRequestForImagesAndVideos(): Array<String> =
private fun getPermissionToRequestForImagesAndVideos(): Array<String> =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arrayOf(Manifest.permission.READ_MEDIA_VIDEO, Manifest.permission.READ_MEDIA_IMAGES)
} else {

View file

@ -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"),
}

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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()

View file

@ -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()
}
}

View file

@ -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)

View file

@ -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<Account, CreateAccount.Params>() {
private val metricsProvider: MetricsProvider,
dispatcher: AppCoroutineDispatchers
) : ResultInteractor<CreateAccount.Params, Account>(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
}
/**

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

@ -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)
}

View file

@ -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<Unit, NetworkModeConfig>(dispatchers.io) {
override suspend fun doWork(params: Unit): NetworkModeConfig {
return repository.getNetworkMode()
}
}

View file

@ -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<SetNetworkMode.Params, Unit>(dispatchers.io) {
override suspend fun doWork(params: Params) {
repository.setNetworkMode(params.networkModeConfig)
}
data class Params(val networkModeConfig: NetworkModeConfig)
}

View file

@ -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(

View file

@ -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,

View file

@ -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)
}
}

View file

@ -826,6 +826,21 @@
<string name="debug_mode">debug_mode</string>
<string name="trouble_mode">trouble_mode</string>
<string name="settings_screen">Settings</string>
<string name="settings_network_mode">Network mode</string>
<string name="settings_network_configuration_file">Network Configuration File</string>
<string name="settings_network_configuration_file_choose">Choose file</string>
<string-array name="settings_networks_entries">
<item>Default</item>
<item>Local-only Mode</item>
<item>Custom</item>
</string-array>
<array name="settings_networks_entries_values">
<item>default</item>
<item>local</item>
<item>custom</item>
</array>
<string name="snack_link_to">linked to</string>
<string name="snack_move_to">moved to</string>

View file

@ -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()

View file

@ -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(

View file

@ -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

View file

@ -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
}

View file

@ -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"
}
}

View file

@ -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"

View file

@ -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)

View file

@ -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))
}

View file

@ -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) {

View file

@ -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)
}
}
},
)
}
}

View file

@ -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))
}

View file

@ -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 <T : ViewModel> create(
modelClass: Class<T>
): T = PreferencesViewModel(
copyFileToCache = copyFileToCacheDirectory,
getNetworkMode = getNetworkMode,
setNetworkMode = setNetworkMode
) as T
}
}

View file

@ -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<Context>? = 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<String?, String?> {
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)
}
/**
* Return /storage/emulated/0/Android/data/io.anytype.app/files/networkModeConfig directory
*/
private fun Context.getExternalCustomNetworkDirTemp(): File? = getExternalFilesDir("networkModeConfig")