mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-08 05:47:05 +09:00
DROID-449 Research settings of app to add debug (#2725)
DROID-449 App | Tech | Add debug screen
This commit is contained in:
parent
258c05183f
commit
548f3b0039
53 changed files with 838 additions and 108 deletions
|
@ -176,6 +176,7 @@ dependencies {
|
|||
implementation applicationDependencies.composeFoundation
|
||||
implementation applicationDependencies.composeMaterial
|
||||
implementation applicationDependencies.composeToolingPreview
|
||||
implementation applicationDependencies.preference
|
||||
debugImplementation applicationDependencies.composeTooling
|
||||
|
||||
implementation databaseDependencies.room
|
||||
|
|
|
@ -23,9 +23,9 @@
|
|||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name="com.anytypeio.anytype.ui.main.MainActivity"
|
||||
android:screenOrientation="portrait"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter android:label="filter">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -43,11 +43,23 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.settings.system.SettingsActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleInstance"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
android:exported="false"
|
||||
android:screenOrientation="fullSensor"
|
||||
tools:replace="screenOrientation" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
|
|
|
@ -1,24 +1,32 @@
|
|||
package com.anytypeio.anytype.app
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.anytypeio.anytype.BuildConfig
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
class DefaultFeatureToggles @Inject constructor() : FeatureToggles {
|
||||
class DefaultFeatureToggles @Inject constructor(
|
||||
private val context: Context,
|
||||
@TogglePrefs private val prefs: SharedPreferences
|
||||
) : FeatureToggles {
|
||||
|
||||
override val isLogFromMiddlewareLibrary =
|
||||
BuildConfig.LOG_FROM_MW_LIBRARY && BuildConfig.DEBUG
|
||||
BuildConfig.LOG_FROM_MW_LIBRARY && isDebug
|
||||
|
||||
override val isLogMiddlewareInteraction =
|
||||
BuildConfig.LOG_MW_INTERACTION && BuildConfig.DEBUG
|
||||
BuildConfig.LOG_MW_INTERACTION && isDebug
|
||||
|
||||
override val isLogDashboardReducer =
|
||||
BuildConfig.LOG_DASHBOARD_REDUCER && BuildConfig.DEBUG
|
||||
BuildConfig.LOG_DASHBOARD_REDUCER && isDebug
|
||||
|
||||
override val isLogEditorViewModelEvents =
|
||||
BuildConfig.LOG_EDITOR_VIEWMODEL_EVENTS && BuildConfig.DEBUG
|
||||
BuildConfig.LOG_EDITOR_VIEWMODEL_EVENTS && isDebug
|
||||
|
||||
override val isLogEditorControlPanelMachine =
|
||||
BuildConfig.LOG_EDITOR_CONTROL_PANEL && BuildConfig.DEBUG
|
||||
BuildConfig.LOG_EDITOR_CONTROL_PANEL && isDebug
|
||||
|
||||
override val isDebug: Boolean
|
||||
get() = prefs.getBoolean(context.getString(R.string.debug_mode), BuildConfig.DEBUG)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.anytypeio.anytype.app
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@MustBeDocumented
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class TogglePrefs
|
|
@ -1,5 +1,6 @@
|
|||
package com.anytypeio.anytype.di.feature.settings
|
||||
|
||||
import android.content.Context
|
||||
import com.anytypeio.anytype.analytics.base.Analytics
|
||||
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
|
||||
import com.anytypeio.anytype.domain.account.DeleteAccount
|
||||
|
@ -7,9 +8,15 @@ import com.anytypeio.anytype.domain.auth.interactor.Logout
|
|||
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.domain.config.ConfigStorage
|
||||
import com.anytypeio.anytype.domain.debugging.DebugSync
|
||||
import com.anytypeio.anytype.domain.device.ClearFileCache
|
||||
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
|
||||
import com.anytypeio.anytype.providers.DefaultUriFileProvider
|
||||
import com.anytypeio.anytype.ui.settings.AccountAndDataFragment
|
||||
import com.anytypeio.anytype.ui_settings.account.AccountAndDataViewModel
|
||||
import com.anytypeio.anytype.ui_settings.account.repo.DebugSyncShareDownloader
|
||||
import com.anytypeio.anytype.ui_settings.account.repo.FileSaver
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
|
@ -27,21 +34,42 @@ interface AccountAndDataSubComponent {
|
|||
fun inject(fragment: AccountAndDataFragment)
|
||||
}
|
||||
|
||||
@Module
|
||||
@Module(includes = [AccountAndDataModule.Bindings::class])
|
||||
object AccountAndDataModule {
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideViewModelFactory(
|
||||
clearFileCache: ClearFileCache,
|
||||
deleteAccount: DeleteAccount,
|
||||
debugSyncShareDownloader: DebugSyncShareDownloader,
|
||||
analytics: Analytics
|
||||
): AccountAndDataViewModel.Factory = AccountAndDataViewModel.Factory(
|
||||
clearFileCache = clearFileCache,
|
||||
deleteAccount = deleteAccount,
|
||||
debugSyncShareDownloader = debugSyncShareDownloader,
|
||||
analytics = analytics
|
||||
)
|
||||
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideDebugSync(repo: BlockRepository): DebugSync = DebugSync(repo = repo)
|
||||
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun provideFileSaver(
|
||||
uriFileProvider: UriFileProvider,
|
||||
context: Context
|
||||
): FileSaver = FileSaver(context, uriFileProvider)
|
||||
|
||||
@Provides
|
||||
@PerScreen
|
||||
fun providesDebugSyncShareDownloader(
|
||||
debugSync: DebugSync,
|
||||
fileSaver: FileSaver
|
||||
): DebugSyncShareDownloader = DebugSyncShareDownloader(debugSync, fileSaver)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@PerScreen
|
||||
|
@ -62,4 +90,14 @@ object AccountAndDataModule {
|
|||
@Provides
|
||||
@PerScreen
|
||||
fun deleteAccount(repo: AuthRepository): DeleteAccount = DeleteAccount(repo)
|
||||
|
||||
@Module
|
||||
interface Bindings {
|
||||
|
||||
@PerScreen
|
||||
@Binds
|
||||
fun bindUriFileProvider(
|
||||
defaultProvider: DefaultUriFileProvider
|
||||
): UriFileProvider
|
||||
}
|
||||
}
|
|
@ -226,8 +226,9 @@ object DataModule {
|
|||
fun provideMiddleware(
|
||||
service: MiddlewareService,
|
||||
factory: MiddlewareFactory,
|
||||
logger: MiddlewareProtobufLogger
|
||||
): Middleware = Middleware(service, factory, logger)
|
||||
logger: MiddlewareProtobufLogger,
|
||||
protobufConverter: ProtobufConverterProvider
|
||||
): Middleware = Middleware(service, factory, logger, protobufConverter)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package com.anytypeio.anytype.di.main
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.anytypeio.anytype.app.TogglePrefs
|
||||
import com.anytypeio.anytype.app.DefaultFeatureToggles
|
||||
import com.anytypeio.anytype.core_utils.tools.DefaultUrlValidator
|
||||
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
|
||||
|
@ -21,6 +25,13 @@ object UtilModule {
|
|||
@Singleton
|
||||
fun provideUrlBuilder(gateway: Gateway): UrlBuilder = UrlBuilder(gateway)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
@TogglePrefs
|
||||
fun providesSharedPreferences(
|
||||
context: Context
|
||||
): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
@Module
|
||||
interface Bindings {
|
||||
|
|
|
@ -31,7 +31,6 @@ import com.anytypeio.anytype.presentation.navigation.AppNavigation
|
|||
import com.anytypeio.anytype.presentation.wallpaper.WallpaperColor
|
||||
import com.anytypeio.anytype.ui.editor.CreateObjectFragment
|
||||
import com.anytypeio.anytype.ui_settings.appearance.ThemeApplicator
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package com.anytypeio.anytype.ui.settings
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -10,11 +13,13 @@ import androidx.compose.ui.platform.ComposeView
|
|||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.anytypeio.anytype.BuildConfig
|
||||
import com.anytypeio.anytype.R
|
||||
import com.anytypeio.anytype.analytics.base.EventsDictionary
|
||||
import com.anytypeio.anytype.core_utils.ext.subscribe
|
||||
import com.anytypeio.anytype.core_utils.ext.toast
|
||||
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
|
||||
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetComposeFragment
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.ui.auth.account.DeleteAccountWarning
|
||||
|
@ -22,6 +27,7 @@ import com.anytypeio.anytype.ui.dashboard.ClearCacheAlertFragment
|
|||
import com.anytypeio.anytype.ui.profile.KeychainPhraseDialog
|
||||
import com.anytypeio.anytype.ui_settings.account.AccountAndDataScreen
|
||||
import com.anytypeio.anytype.ui_settings.account.AccountAndDataViewModel
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
||||
|
@ -29,10 +35,14 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
|||
@Inject
|
||||
lateinit var factory: AccountAndDataViewModel.Factory
|
||||
|
||||
@Inject
|
||||
lateinit var toggles: FeatureToggles
|
||||
|
||||
private val vm by viewModels<AccountAndDataViewModel> { factory }
|
||||
|
||||
private val onKeychainPhraseClicked = {
|
||||
val bundle = bundleOf(KeychainPhraseDialog.ARG_SCREEN_TYPE to EventsDictionary.Type.screenSettings)
|
||||
val bundle =
|
||||
bundleOf(KeychainPhraseDialog.ARG_SCREEN_TYPE to EventsDictionary.Type.screenSettings)
|
||||
findNavController().navigate(R.id.keychainDialog, bundle)
|
||||
}
|
||||
|
||||
|
@ -45,6 +55,11 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
|||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
jobs += lifecycleScope.subscribe(vm.debugSyncReportUri) { uri ->
|
||||
if (uri != null) {
|
||||
shareFile(uri)
|
||||
}
|
||||
}
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
|
@ -54,14 +69,36 @@ class AccountAndDataFragment : BaseBottomSheetComposeFragment() {
|
|||
onClearFileCachedClicked = { proceedWithClearFileCacheWarning() },
|
||||
onDeleteAccountClicked = { proceedWithAccountDeletion() },
|
||||
onLogoutClicked = onLogoutClicked,
|
||||
onDebugSyncReportClicked = { vm.onDebugSyncReportClicked() },
|
||||
isLogoutInProgress = vm.isLoggingOut.collectAsState().value,
|
||||
isClearCacheInProgress = vm.isClearFileCacheInProgress.collectAsState().value
|
||||
isClearCacheInProgress = vm.isClearFileCacheInProgress.collectAsState().value,
|
||||
isDebugSyncReportInProgress = vm.isDebugSyncReportInProgress.collectAsState().value,
|
||||
isShowDebug = toggles.isDebug
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareFile(uri: Uri) {
|
||||
try {
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
type = requireContext().contentResolver.getType(uri)
|
||||
}
|
||||
startActivity(shareIntent)
|
||||
} catch (e: Exception) {
|
||||
if (e is ActivityNotFoundException) {
|
||||
toast("No application found to open the selected file")
|
||||
} else {
|
||||
toast("Could not open file: ${e.message}")
|
||||
}
|
||||
Timber.e(e, "Error while opening file")
|
||||
}
|
||||
}
|
||||
|
||||
private fun proceedWithClearFileCacheWarning() {
|
||||
vm.onClearCacheButtonClicked()
|
||||
val dialog = ClearCacheAlertFragment.new()
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.anytypeio.anytype.core_utils.ext.visible
|
|||
import com.anytypeio.anytype.core_utils.ui.BaseFragment
|
||||
import com.anytypeio.anytype.databinding.FragmentDebugSettingsBinding
|
||||
import com.anytypeio.anytype.di.common.componentManager
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.config.GetDebugSettings
|
||||
import com.anytypeio.anytype.domain.config.UseCustomContextMenu
|
||||
import com.anytypeio.anytype.domain.debugging.DebugLocalStore
|
||||
|
@ -54,9 +55,8 @@ class DebugSettingsFragment : BaseFragment<FragmentDebugSettingsBinding>(R.layou
|
|||
|
||||
binding.btnSync.setOnClickListener {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
debugSync.invoke(Unit).proceed(
|
||||
failure = {},
|
||||
success = { status -> saveToFile(status) }
|
||||
debugSync.execute(Unit).fold(
|
||||
onSuccess = { status -> saveToFile(status) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
package com.anytypeio.anytype.ui.settings.system
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.anytypeio.anytype.R
|
||||
|
||||
class PreferenceFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences, rootKey)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package com.anytypeio.anytype.ui.settings.system
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.anytypeio.anytype.R
|
||||
|
||||
class SettingsActivity : AppCompatActivity(R.layout.activity_settings)
|
23
app/src/main/res/layout/activity_settings.xml
Normal file
23
app/src/main/res/layout/activity_settings.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/dashboard_background"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
style="@style/DashboardGreetingTextStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="20dp"
|
||||
android:text="@string/settings_screen"
|
||||
android:textColor="@color/text_primary" />
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/fragment"
|
||||
android:name="com.anytypeio.anytype.ui.settings.system.PreferenceFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:context=".ui.settings.system.SettingsActivity" />
|
||||
</LinearLayout>
|
|
@ -291,6 +291,8 @@ Do the computation of an expensive paragraph of text on a background thread:
|
|||
<item quantity="one">%d object selected</item>
|
||||
<item quantity="other">%d objects selected</item>
|
||||
</plurals>
|
||||
<string name="debug_mode">debug_mode</string>
|
||||
<string name="settings_screen">Settings</string>
|
||||
|
||||
<string name="snack_link_to">linked to</string>
|
||||
<string name="recovery_phrase_copied">Recovery phrase copied</string>
|
||||
|
|
7
app/src/main/res/xml/preferences.xml
Normal file
7
app/src/main/res/xml/preferences.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<SwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="@string/debug_mode"
|
||||
android:title="Debug mode" />
|
||||
</PreferenceScreen>
|
|
@ -12,4 +12,6 @@ interface FeatureToggles {
|
|||
|
||||
val isLogEditorControlPanelMachine: Boolean
|
||||
|
||||
val isDebug: Boolean
|
||||
|
||||
}
|
|
@ -4,9 +4,20 @@ import android.os.Bundle
|
|||
import android.view.View
|
||||
import com.anytypeio.anytype.core_utils.R
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
abstract class BaseBottomSheetComposeFragment : BottomSheetDialogFragment() {
|
||||
|
||||
protected val jobs = mutableListOf<Job>()
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
jobs.apply {
|
||||
forEach { it.cancel() }
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
injectDependencies()
|
||||
|
|
|
@ -459,6 +459,10 @@ class BlockDataRepository(
|
|||
}
|
||||
|
||||
override suspend fun debugSync(): String = remote.debugSync()
|
||||
|
||||
override suspend fun debugTree(objectId: Id, path: String): String
|
||||
= remote.debugTree(objectId = objectId, path = path)
|
||||
|
||||
override suspend fun debugLocalStore(path: String): String =
|
||||
remote.debugLocalStore(path)
|
||||
|
||||
|
|
|
@ -171,6 +171,9 @@ interface BlockDataStore {
|
|||
suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload
|
||||
|
||||
suspend fun debugSync(): String
|
||||
|
||||
suspend fun debugTree(objectId: Id, path: String): String
|
||||
|
||||
suspend fun debugLocalStore(path: String): String
|
||||
|
||||
suspend fun turnInto(
|
||||
|
|
|
@ -178,6 +178,9 @@ interface BlockRemote {
|
|||
suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload
|
||||
|
||||
suspend fun debugSync(): String
|
||||
|
||||
suspend fun debugTree(objectId: Id, path: String): String
|
||||
|
||||
suspend fun debugLocalStore(path: String): String
|
||||
|
||||
suspend fun updateDetail(
|
||||
|
|
|
@ -383,6 +383,10 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore {
|
|||
): Payload = remote.deleteRelationFromObject(ctx = ctx, relation = relation)
|
||||
|
||||
override suspend fun debugSync(): String = remote.debugSync()
|
||||
|
||||
override suspend fun debugTree(objectId: Id, path: String): String =
|
||||
remote.debugTree(objectId = objectId, path = path)
|
||||
|
||||
override suspend fun debugLocalStore(path: String): String = remote.debugLocalStore(path)
|
||||
|
||||
override suspend fun turnInto(
|
||||
|
|
|
@ -10,6 +10,7 @@ ext {
|
|||
androidx_core_testing_version = '2.1.0'
|
||||
androidx_security_crypto_version = '1.0.0'
|
||||
androidx_compose_version = '1.2.1'
|
||||
androidx_preference_version = '1.2.0'
|
||||
|
||||
// Other Android framework dependencies
|
||||
appcompat_version = '1.3.0'
|
||||
|
@ -137,44 +138,45 @@ ext {
|
|||
composeTooling: "androidx.compose.ui:ui-tooling:$androidx_compose_version",
|
||||
composeToolingPreview: "androidx.compose.ui:ui-tooling-preview:$androidx_compose_version",
|
||||
composeFoundation: "androidx.compose.foundation:foundation:$androidx_compose_version",
|
||||
composeMaterial: "androidx.compose.material:material:$androidx_compose_version"
|
||||
composeMaterial: "androidx.compose.material:material:$androidx_compose_version",
|
||||
preference : "androidx.preference:preference:$androidx_preference_version"
|
||||
]
|
||||
|
||||
unitTesting = [
|
||||
kotlin: "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version",
|
||||
kotlinTest: "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version",
|
||||
robolectricLatest: "org.robolectric:robolectric:$robolectric_latest_version",
|
||||
junit: "junit:junit:$junit_version",
|
||||
mockitoKotlin: "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version",
|
||||
kluent: "org.amshove.kluent:kluent:$kluent_version",
|
||||
archCoreTesting: "androidx.arch.core:core-testing:$androidx_core_testing_version",
|
||||
liveDataTesting: "com.jraska.livedata:testing-ktx:$live_data_testing_version",
|
||||
coroutineTesting: "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_testing_version",
|
||||
assertj: "com.squareup.assertj:assertj-android:1.0.0",
|
||||
androidXTestCore: "androidx.test:core:$androidx_test_core_version",
|
||||
timberJUnit: "net.lachlanmckee:timber-junit-rule:$timber_junit",
|
||||
turbine: "app.cash.turbine:turbine:$turbine_version"
|
||||
kotlin : "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version",
|
||||
kotlinTest : "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version",
|
||||
robolectricLatest: "org.robolectric:robolectric:$robolectric_latest_version",
|
||||
junit : "junit:junit:$junit_version",
|
||||
mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version",
|
||||
kluent : "org.amshove.kluent:kluent:$kluent_version",
|
||||
archCoreTesting : "androidx.arch.core:core-testing:$androidx_core_testing_version",
|
||||
liveDataTesting : "com.jraska.livedata:testing-ktx:$live_data_testing_version",
|
||||
coroutineTesting : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_testing_version",
|
||||
assertj : "com.squareup.assertj:assertj-android:1.0.0",
|
||||
androidXTestCore : "androidx.test:core:$androidx_test_core_version",
|
||||
timberJUnit : "net.lachlanmckee:timber-junit-rule:$timber_junit",
|
||||
turbine : "app.cash.turbine:turbine:$turbine_version"
|
||||
]
|
||||
|
||||
acceptanceTesting = [
|
||||
androidJUnit: "androidx.test.ext:junit:$android_junit_version",
|
||||
testRunner: "androidx.test:runner:$runner_version",
|
||||
testRules: "androidx.test:rules:$runner_version",
|
||||
espressoCore: "androidx.test.espresso:espresso-core:$espresso_version",
|
||||
espressoContrib: "androidx.test.espresso:espresso-contrib:$espresso_version",
|
||||
espressoIntents: "androidx.test.espresso:espresso-intents:$espresso_version",
|
||||
androidJUnit : "androidx.test.ext:junit:$android_junit_version",
|
||||
testRunner : "androidx.test:runner:$runner_version",
|
||||
testRules : "androidx.test:rules:$runner_version",
|
||||
espressoCore : "androidx.test.espresso:espresso-core:$espresso_version",
|
||||
espressoContrib : "androidx.test.espresso:espresso-contrib:$espresso_version",
|
||||
espressoIntents : "androidx.test.espresso:espresso-intents:$espresso_version",
|
||||
androidAnnotations: "androidx.annotation:annotation:$appcompat_version",
|
||||
mockitoKotlin: "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version",
|
||||
mockitoAndroid: "org.mockito:mockito-android:$mockito_android_version",
|
||||
disableAnimation: "com.bartoszlipinski:disable-animations-rule:$disable_animation_version",
|
||||
fragmentTesting: "androidx.fragment:fragment-testing:$fragment_version",
|
||||
navigationTesting: "androidx.navigation:navigation-testing:$navigation_version"
|
||||
mockitoKotlin : "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version",
|
||||
mockitoAndroid : "org.mockito:mockito-android:$mockito_android_version",
|
||||
disableAnimation : "com.bartoszlipinski:disable-animations-rule:$disable_animation_version",
|
||||
fragmentTesting : "androidx.fragment:fragment-testing:$fragment_version",
|
||||
navigationTesting : "androidx.navigation:navigation-testing:$navigation_version"
|
||||
]
|
||||
|
||||
development = [
|
||||
leakCanary: "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}",
|
||||
leakCanaryNoop: "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryVersion}",
|
||||
stetho: "com.facebook.stetho:stetho:${stethoVersion}"
|
||||
leakCanary : "com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}",
|
||||
leakCanaryNoop: "com.squareup.leakcanary:leakcanary-android-no-op:${leakCanaryVersion}",
|
||||
stetho : "com.facebook.stetho:stetho:${stethoVersion}"
|
||||
]
|
||||
|
||||
protobuf = [
|
||||
|
@ -185,18 +187,18 @@ ext {
|
|||
]
|
||||
|
||||
db = [
|
||||
room: "androidx.room:room-runtime:$room_version",
|
||||
roomKtx: "androidx.room:room-ktx:$room_version",
|
||||
annotations: "androidx.room:room-compiler:$room_version",
|
||||
roomTesting: "androidx.room:room-testing:$room_version"
|
||||
room : "androidx.room:room-runtime:$room_version",
|
||||
roomKtx : "androidx.room:room-ktx:$room_version",
|
||||
annotations: "androidx.room:room-compiler:$room_version",
|
||||
roomTesting: "androidx.room:room-testing:$room_version"
|
||||
]
|
||||
|
||||
analytics = [
|
||||
amplitude: "com.amplitude:android-sdk:$amplitude_version",
|
||||
okhttp: "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
amplitude: "com.amplitude:android-sdk:$amplitude_version",
|
||||
okhttp : "com.squareup.okhttp3:okhttp:$okhttp_version"
|
||||
]
|
||||
|
||||
anytype = [
|
||||
middleware: "io.anytype:android-mw:$middleware_version"
|
||||
middleware: "io.anytype:android-mw:$middleware_version"
|
||||
]
|
||||
}
|
|
@ -4,9 +4,13 @@ import com.anytypeio.anytype.domain.base.Interactor.Status
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.Result
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
/**
|
||||
|
@ -51,20 +55,24 @@ abstract class Interactor<in P>(
|
|||
}
|
||||
}
|
||||
|
||||
abstract class ResultInteractor<in P, R> {
|
||||
operator fun invoke(params: P): Flow<R> = flow { emit(doWork(params)) }
|
||||
abstract class ResultInteractor<in P, R>(
|
||||
private val context: CoroutineContext = Dispatchers.IO
|
||||
) {
|
||||
fun asFlow(params: P): Flow<R> = flow { emit(doWork(params)) }.flowOn(context)
|
||||
|
||||
fun stream(params: P): Flow<Resultat<R>> {
|
||||
return asFlow(params)
|
||||
.map {
|
||||
@Suppress("USELESS_CAST")
|
||||
Resultat.Success(run(params)) as Resultat<R>
|
||||
}
|
||||
.onStart { emit(Resultat.Loading()) }
|
||||
.catch { e -> emit(Resultat.Failure(e)) }
|
||||
.flowOn(context)
|
||||
}
|
||||
|
||||
suspend fun run(params: P) = doWork(params)
|
||||
suspend fun execute(params: P): Result<R> = runCatching { doWork(params) }
|
||||
suspend fun execute(params: P): Resultat<R> = runCatchingL { doWork(params) }
|
||||
|
||||
protected abstract suspend fun doWork(params: P): R
|
||||
}
|
||||
|
||||
suspend fun <R, T> Result<T>.suspendFold(
|
||||
onSuccess: suspend (value: T) -> R,
|
||||
onFailure: suspend (Throwable) -> R
|
||||
): R {
|
||||
return when (val exception = exceptionOrNull()) {
|
||||
null -> onSuccess(getOrNull() as T)
|
||||
else -> onFailure(exception)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,10 @@ package com.anytypeio.anytype.domain.base
|
|||
|
||||
import com.anytypeio.anytype.domain.error.Error
|
||||
|
||||
@Deprecated(
|
||||
message = "Result is deprecated",
|
||||
replaceWith = ReplaceWith("Resultat", "com.anytypeio.anytype.domain.base")
|
||||
)
|
||||
sealed class Result<T> {
|
||||
data class Success<T>(val data: T) : Result<T>()
|
||||
data class Failure<T>(val error: Error) : Result<T>()
|
||||
|
|
|
@ -0,0 +1,339 @@
|
|||
package com.anytypeio.anytype.domain.base
|
||||
|
||||
import kotlin.Result
|
||||
|
||||
/*
|
||||
* Copyright 2022 Nicolas Haan.
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.md file.
|
||||
*/
|
||||
/**
|
||||
* A sealed class that encapsulates a successful outcome with a value of type [T]
|
||||
* or a failure with an arbitrary [Throwable] exception,
|
||||
* or a loading state
|
||||
* @see: This is a fork of Kotlin [kotlin.Result] class with an additional Loading state,
|
||||
* but preserving Result API
|
||||
*/
|
||||
sealed class Resultat<out T> {
|
||||
|
||||
/**
|
||||
* This type represent a successful outcome.
|
||||
* @param value The encapsulated successful value
|
||||
*/
|
||||
data class Success<out T>(val value: T) : Resultat<T>() {
|
||||
override fun toString(): String = "Success($value)"
|
||||
}
|
||||
|
||||
/**
|
||||
* This type represents a failed outcome.
|
||||
* @param exception The encapsulated exception value
|
||||
*/
|
||||
data class Failure(val exception: Throwable) : Resultat<Nothing>() {
|
||||
override fun toString(): String = "Failure($exception)"
|
||||
}
|
||||
|
||||
/**
|
||||
* This type represents a loading state.
|
||||
*/
|
||||
class Loading : Resultat<Nothing>() {
|
||||
override fun toString(): String = "Loading"
|
||||
}
|
||||
|
||||
// discovery
|
||||
|
||||
/**
|
||||
* Returns `true` if this instance represents a successful outcome.
|
||||
* In this case [isFailure] returns `false`.
|
||||
* In this case [isLoading] returns `false`.
|
||||
*/
|
||||
val isSuccess: Boolean get() = this is Success
|
||||
|
||||
/**
|
||||
* Returns `true` if this instance represents a failed outcome.
|
||||
* In this case [isSuccess] returns `false`.
|
||||
* In this case [isLoading] returns `false`.
|
||||
*/
|
||||
val isFailure: Boolean get() = this is Failure
|
||||
|
||||
/**
|
||||
* Returns `true` if this instance represents a loading outcome.
|
||||
* In this case [isSuccess] returns `false`.
|
||||
* In this case [isFailure] returns `false`.
|
||||
*/
|
||||
val isLoading: Boolean get() = this is Loading
|
||||
|
||||
// value & exception retrieval
|
||||
|
||||
/**
|
||||
* Returns the encapsulated value if this instance represents [success][Resultat.isSuccess] or `null`
|
||||
* if it is [failure][Resultat.isFailure] or [Resultat.Loading].
|
||||
*/
|
||||
inline fun getOrNull(): T? =
|
||||
when (this) {
|
||||
is Failure -> null
|
||||
is Loading -> null
|
||||
is Success -> value
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated [Throwable] exception if this instance represents [failure][isFailure] or `null`
|
||||
* if it is [success][isSuccess] or [loading][isLoading].
|
||||
*/
|
||||
inline fun exceptionOrNull(): Throwable? =
|
||||
when (this) {
|
||||
is Failure -> exception
|
||||
is Success -> null
|
||||
is Loading -> null
|
||||
}
|
||||
|
||||
// companion with constructors
|
||||
|
||||
/**
|
||||
* Companion object for [Resultat] class that contains its constructor functions
|
||||
* [success], [loading] and [failure].
|
||||
*/
|
||||
companion object {
|
||||
/**
|
||||
* Returns an instance that encapsulates the given [value] as successful value.
|
||||
*/
|
||||
inline fun <T> success(value: T): Resultat<T> = Success(value)
|
||||
|
||||
/**
|
||||
* Returns an instance that encapsulates the given [Throwable] [exception] as failure.
|
||||
*/
|
||||
inline fun <T> failure(exception: Throwable): Resultat<T> = Failure(exception)
|
||||
|
||||
/**
|
||||
* Returns an instance that represents the loading state.
|
||||
*/
|
||||
inline fun <T> loading(): Resultat<T> = Loading()
|
||||
}
|
||||
}
|
||||
|
||||
private val loadingException = Throwable("No value available: Loading")
|
||||
|
||||
|
||||
/**
|
||||
* Calls the specified function [block] with `this` value as its receiver and returns its encapsulated result if invocation was successful,
|
||||
* catching any [Throwable] exception that was thrown from the [block] function execution and encapsulating it as a failure.
|
||||
*/
|
||||
inline fun <T, R> T.runCatchingL(block: T.() -> R): Resultat<R> {
|
||||
return try {
|
||||
Resultat.success(block())
|
||||
} catch (e: Throwable) {
|
||||
Resultat.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
// -- extensions ---
|
||||
|
||||
/**
|
||||
* Returns the encapsulated value if this instance represents [success][Resultat.isSuccess] or throws the encapsulated [Throwable] exception
|
||||
* if it is [failure][Resultat.isFailure].
|
||||
*
|
||||
* This function is a shorthand for `getOrElse { throw it }` (see [getOrElse]).
|
||||
*/
|
||||
fun <T> Resultat<T>.getOrThrow(): T {
|
||||
when (this) {
|
||||
is Resultat.Failure -> throw exception
|
||||
is Resultat.Loading -> throw loadingException
|
||||
is Resultat.Success -> return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated value if this instance represents [success][Resultat.isSuccess] or the
|
||||
* result of [onFailure] function for the encapsulated [Throwable] exception if it is [failure][Resultat.isFailure]
|
||||
* or is [loading][Resultat.Loading].
|
||||
*
|
||||
* Note, that this function rethrows any [Throwable] exception thrown by [onFailure] function.
|
||||
*
|
||||
*/
|
||||
fun <R, T : R> Resultat<T>.getOrElse(onFailure: (exception: Throwable) -> R): R {
|
||||
return when (this) {
|
||||
is Resultat.Failure -> onFailure(exception)
|
||||
is Resultat.Loading -> onFailure(loadingException)
|
||||
is Resultat.Success -> value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated value if this instance represents [success][Resultat.isSuccess] or the
|
||||
* [defaultValue] if it is [failure][Resultat.isFailure] or [loading][Resultat.isLoading].
|
||||
*
|
||||
* This function is a shorthand for `getOrElse { defaultValue }` (see [getOrElse]).
|
||||
*/
|
||||
inline fun <R, T : R> Resultat<T>.getOrDefault(defaultValue: R): R {
|
||||
return when (this) {
|
||||
is Resultat.Failure -> defaultValue
|
||||
is Resultat.Loading -> defaultValue
|
||||
is Resultat.Success -> value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of [onSuccess] for the encapsulated value if this instance represents [success][Resultat.isSuccess]
|
||||
* or the result of [onFailure] function for the encapsulated [Throwable] exception if it is [failure][Resultat.isFailure].
|
||||
* or the result of [onLoading] function if it is [loading][Resultat.isLoading].
|
||||
*
|
||||
* Note, that this function rethrows any [Throwable] exception thrown by [onSuccess] or by [onFailure] function.
|
||||
*/
|
||||
inline fun <T> Resultat<T>.fold(
|
||||
onSuccess: (value: T) -> Unit,
|
||||
onFailure: (exception: Throwable) -> Unit = {},
|
||||
onLoading: () -> Unit = {},
|
||||
) {
|
||||
return when (this) {
|
||||
is Resultat.Failure -> onFailure(exception)
|
||||
is Resultat.Loading -> onLoading()
|
||||
is Resultat.Success -> onSuccess(value)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> Resultat<T>.suspendFold(
|
||||
onSuccess: suspend (value: T) -> Unit,
|
||||
onFailure: suspend (Throwable) -> Unit = {},
|
||||
onLoading: suspend () -> Unit = {},
|
||||
): Unit {
|
||||
return when (this) {
|
||||
is Resultat.Failure -> onFailure(exception)
|
||||
is Resultat.Loading -> onLoading()
|
||||
is Resultat.Success -> onSuccess(value)
|
||||
}
|
||||
}
|
||||
|
||||
// transformation
|
||||
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to the encapsulated value
|
||||
* if this instance represents [success][Resultat.isSuccess] or the
|
||||
* original encapsulated [Throwable] exception if it is [failure][Resultat.isFailure] or [loading][Resultat.Loading].
|
||||
*
|
||||
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
|
||||
* See [mapCatching] for an alternative that encapsulates exceptions.
|
||||
*/
|
||||
inline fun <R, T> Resultat<T>.map(transform: (value: T) -> R): Resultat<R> {
|
||||
return when (this) {
|
||||
is Resultat.Failure -> this
|
||||
is Resultat.Success -> Resultat.success(transform(value))
|
||||
is Resultat.Loading -> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to the encapsulated value
|
||||
* if this instance represents [success][Resultat.isSuccess] or the
|
||||
* original encapsulated [Throwable] exception if it is [failure][Resultat.isFailure]
|
||||
* or the loading state if it [loading][Resultat.Loading].
|
||||
*
|
||||
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a failure.
|
||||
* See [map] for an alternative that rethrows exceptions from `transform` function.
|
||||
*/
|
||||
inline fun <R, T> Resultat<T>.mapCatching(transform: (value: T) -> R): Resultat<R> {
|
||||
return when (this) {
|
||||
is Resultat.Failure -> Resultat.Failure(exception)
|
||||
is Resultat.Success -> runCatchingL { transform(value) }
|
||||
is Resultat.Loading -> this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to the encapsulated [Throwable] exception
|
||||
* if this instance represents [failure][Resultat.isFailure] or the
|
||||
* original encapsulated value if it is [success][Resultat.isSuccess].
|
||||
*
|
||||
* @param recoverLoading Whether loading state calls transform or exposes untouched loading state
|
||||
* Note, that this function rethrows any [Throwable] exception thrown by [transform] function.
|
||||
* See [recoverCatching] for an alternative that encapsulates exceptions.
|
||||
*/
|
||||
inline fun <R, T : R> Resultat<T>.recover(
|
||||
recoverLoading: Boolean = false,
|
||||
transform: (exception: Throwable) -> R,
|
||||
): Resultat<R> {
|
||||
|
||||
return when (this) {
|
||||
is Resultat.Success -> this
|
||||
is Resultat.Failure -> Resultat.success(transform(exception))
|
||||
is Resultat.Loading -> if (!recoverLoading) {
|
||||
this
|
||||
} else {
|
||||
Resultat.success(transform(Throwable("No value available: Loading")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated result of the given [transform] function applied to the encapsulated [Throwable] exception
|
||||
* if this instance represents [failure][Resultat.isFailure] or the
|
||||
* original encapsulated value if it is [success][Resultat.isSuccess].
|
||||
*
|
||||
* @param recoverLoading Whether loading state calls transform or exposes untouched loading state
|
||||
* This function catches any [Throwable] exception thrown by [transform] function and encapsulates it as a failure.
|
||||
* See [recover] for an alternative that rethrows exceptions.
|
||||
*/
|
||||
inline fun <R, T : R> Resultat<T>.recoverCatching(
|
||||
recoverLoading: Boolean = false,
|
||||
transform: (exception: Throwable) -> R,
|
||||
): Resultat<R> {
|
||||
return when (this) {
|
||||
is Resultat.Success -> this
|
||||
is Resultat.Failure -> runCatchingL { transform(exception) }
|
||||
is Resultat.Loading -> if (!recoverLoading) {
|
||||
this
|
||||
} else {
|
||||
runCatchingL { transform(Throwable("No value available: Loading")) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "peek" onto value/exception and pipe
|
||||
|
||||
/**
|
||||
* Performs the given [action] on the encapsulated [Throwable] exception if this instance represents [failure][Resultat.isFailure].
|
||||
* Returns the original `Resultat` unchanged.
|
||||
*/
|
||||
inline fun <T> Resultat<T>.onFailure(action: (exception: Throwable) -> Unit): Resultat<T> {
|
||||
exceptionOrNull()?.let { action(it) }
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the given [action] on the encapsulated value if this instance represents [success][Resultat.isSuccess].
|
||||
* Returns the original `Resultat` unchanged.
|
||||
*/
|
||||
inline fun <T> Resultat<T>.onSuccess(action: (value: T) -> Unit): Resultat<T> {
|
||||
if (this is Resultat.Success) action(value)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the given [action] on the encapsulated value if this instance represents [success][Resultat.isLoading].
|
||||
* Returns the original `Resultat` unchanged.
|
||||
*/
|
||||
inline fun <T> Resultat<T>.onLoading(action: () -> Unit): Resultat<T> {
|
||||
if (this is Resultat.Loading) action()
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Kotlin [Result] to Resultat type
|
||||
*/
|
||||
fun <T> Result<T>.toResultat(): Resultat<T> = fold(
|
||||
onFailure = {
|
||||
Resultat.failure(it)
|
||||
}, onSuccess = {
|
||||
Resultat.success(it)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Convert [Resultat] to Kotlin [Result]
|
||||
* if [Resultat] is [Resultat.Loading], null is returned
|
||||
*/
|
||||
fun <T> Resultat<T>.toResult(): Result<T>? {
|
||||
return when (this) {
|
||||
is Resultat.Loading -> null
|
||||
is Resultat.Success -> Result.success(this.value)
|
||||
is Resultat.Failure -> Result.failure(this.exception)
|
||||
}
|
||||
}
|
|
@ -230,6 +230,9 @@ interface BlockRepository {
|
|||
suspend fun deleteRelationFromObject(ctx: Id, relation: Key): Payload
|
||||
|
||||
suspend fun debugSync(): String
|
||||
|
||||
suspend fun debugTree(objectId: Id, path: String): String
|
||||
|
||||
suspend fun debugLocalStore(path: String): String
|
||||
|
||||
suspend fun turnInto(
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package com.anytypeio.anytype.domain.debugging
|
||||
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Either
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
|
||||
class DebugSync(private val repo: BlockRepository) : BaseUseCase<String, Unit>() {
|
||||
class DebugSync(private val repo: BlockRepository) : ResultInteractor<Unit, String>() {
|
||||
|
||||
override suspend fun run(params: Unit): Either<Throwable, String> = safe {
|
||||
repo.debugSync()
|
||||
}
|
||||
override suspend fun doWork(params: Unit): String = repo.debugSync()
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.anytypeio.anytype.domain.debugging
|
||||
|
||||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
|
||||
class DebugTree(private val repo: BlockRepository) : ResultInteractor<DebugTree.Params, Id>() {
|
||||
|
||||
override suspend fun doWork(params: Params): Id =
|
||||
repo.debugTree(objectId = params.objectId, path = params.path)
|
||||
|
||||
data class Params(
|
||||
val objectId: Id,
|
||||
val path: String
|
||||
)
|
||||
}
|
|
@ -27,7 +27,7 @@ class CreateNewObject(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun doWork(params: Unit) = getDefaultEditorType(Unit)
|
||||
override suspend fun doWork(params: Unit) = getDefaultEditorType.asFlow(Unit)
|
||||
.map { it.type }
|
||||
.catch { emit(null) }
|
||||
.map { type ->
|
||||
|
|
|
@ -97,7 +97,7 @@ class CreateNewObjectTest {
|
|||
|
||||
private fun givenGetDefaultObjectType(type: String? = null, name: String? = null) {
|
||||
getDefaultEditorType.stub {
|
||||
onBlocking { invoke(Unit) } doReturn flow {
|
||||
onBlocking { asFlow(Unit) } doReturn flow {
|
||||
emit(
|
||||
GetDefaultEditorType.Response(
|
||||
type,
|
||||
|
|
|
@ -418,6 +418,10 @@ class BlockMiddleware(
|
|||
)
|
||||
|
||||
override suspend fun debugSync(): String = middleware.debugSync()
|
||||
|
||||
override suspend fun debugTree(objectId: Id, path: String): String =
|
||||
middleware.debugTree(objectId = objectId, path = path)
|
||||
|
||||
override suspend fun debugLocalStore(path: String): String =
|
||||
middleware.debugExportLocalStore(path)
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ class Middleware(
|
|||
private val service: MiddlewareService,
|
||||
private val factory: MiddlewareFactory,
|
||||
private val logger: MiddlewareProtobufLogger,
|
||||
private val protobufConverter: ProtobufConverterProvider,
|
||||
) {
|
||||
|
||||
@Throws(Exception::class)
|
||||
|
@ -818,7 +819,16 @@ class Middleware(
|
|||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.debugSync(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.toString()
|
||||
return protobufConverter.provideConverter().toJson(response)
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun debugTree(objectId: Id, path: String): String {
|
||||
val request = Rpc.Debug.Tree.Request(objectId = objectId, path = path)
|
||||
if (BuildConfig.DEBUG) logRequest(request)
|
||||
val response = service.debugTree(request)
|
||||
if (BuildConfig.DEBUG) logResponse(response)
|
||||
return response.filename
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
|
|
|
@ -362,6 +362,9 @@ interface MiddlewareService {
|
|||
@Throws(Exception::class)
|
||||
fun debugSync(request: Rpc.Debug.Sync.Request): Rpc.Debug.Sync.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun debugTree(request: Rpc.Debug.Tree.Request): Rpc.Debug.Tree.Response
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun debugExportLocalStore(request: Rpc.Debug.ExportLocalstore.Request): Rpc.Debug.ExportLocalstore.Response
|
||||
|
||||
|
|
|
@ -580,6 +580,17 @@ class MiddlewareServiceImplementation @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun debugTree(request: Rpc.Debug.Tree.Request): Rpc.Debug.Tree.Response {
|
||||
val encoded = Service.debugSync(Rpc.Debug.Tree.Request.ADAPTER.encode(request))
|
||||
val response = Rpc.Debug.Tree.Response.ADAPTER.decode(encoded)
|
||||
val error = response.error
|
||||
if (error != null && error.code != Rpc.Debug.Tree.Response.Error.Code.NULL) {
|
||||
throw Exception(error.description)
|
||||
} else {
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
override fun debugSync(request: Rpc.Debug.Sync.Request): Rpc.Debug.Sync.Response {
|
||||
val encoded = Service.debugSync(Rpc.Debug.Sync.Request.ADAPTER.encode(request))
|
||||
val response = Rpc.Debug.Sync.Response.ADAPTER.decode(encoded)
|
||||
|
|
|
@ -43,7 +43,7 @@ class MiddlewareTest {
|
|||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.openMocks(this)
|
||||
middleware = Middleware(service, factory, mock())
|
||||
middleware = Middleware(service, factory, mock(), mock())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.anytypeio.anytype.core_utils.tools.FeatureToggles
|
|||
import com.anytypeio.anytype.core_utils.ui.ViewState
|
||||
import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel
|
||||
import com.anytypeio.anytype.domain.auth.interactor.GetProfile
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.block.interactor.Move
|
||||
import com.anytypeio.anytype.domain.config.GetConfig
|
||||
import com.anytypeio.anytype.domain.config.GetDebugSettings
|
||||
|
|
|
@ -55,6 +55,7 @@ import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel
|
|||
import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet
|
||||
import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
||||
import com.anytypeio.anytype.domain.base.Result
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.block.interactor.RemoveLinkMark
|
||||
import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks
|
||||
import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.anytypeio.anytype.core_utils.ext.switchToLatestFrom
|
|||
import com.anytypeio.anytype.core_utils.ext.withLatestFrom
|
||||
import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel
|
||||
import com.anytypeio.anytype.domain.base.Result
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.editor.Editor
|
||||
import com.anytypeio.anytype.domain.error.Error
|
||||
import com.anytypeio.anytype.domain.event.interactor.InterceptEvents
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.objects
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.page.CreatePage
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.anytypeio.anytype.analytics.base.Analytics
|
|||
import com.anytypeio.anytype.core_models.Id
|
||||
import com.anytypeio.anytype.core_models.Payload
|
||||
import com.anytypeio.anytype.domain.`object`.DuplicateObject
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.dashboard.interactor.AddToFavorite
|
||||
import com.anytypeio.anytype.domain.dashboard.interactor.RemoveFromFavorite
|
||||
import com.anytypeio.anytype.domain.misc.UrlBuilder
|
||||
|
|
|
@ -28,6 +28,7 @@ import com.anytypeio.anytype.core_utils.common.EventWrapper
|
|||
import com.anytypeio.anytype.core_utils.ext.cancel
|
||||
import com.anytypeio.anytype.domain.`object`.UpdateDetail
|
||||
import com.anytypeio.anytype.domain.base.Result
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.block.interactor.UpdateText
|
||||
import com.anytypeio.anytype.domain.cover.SetDocCoverImage
|
||||
import com.anytypeio.anytype.domain.dataview.SetDataViewSource
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.anytypeio.anytype.core_models.Id
|
|||
import com.anytypeio.anytype.core_models.SmartBlockType
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Interactor
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.device.ClearFileCache
|
||||
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
|
||||
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
|
||||
|
|
|
@ -19,6 +19,7 @@ import com.anytypeio.anytype.domain.auth.interactor.LaunchAccount
|
|||
import com.anytypeio.anytype.domain.auth.interactor.LaunchWallet
|
||||
import com.anytypeio.anytype.domain.auth.model.AuthStatus
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.launch.GetDefaultEditorType
|
||||
import com.anytypeio.anytype.domain.launch.SetDefaultEditorType
|
||||
import com.anytypeio.anytype.domain.misc.AppActionManager
|
||||
|
|
|
@ -33,7 +33,7 @@ class TemplateViewModel(
|
|||
|
||||
fun onStart(ctx: Id) {
|
||||
viewModelScope.launch {
|
||||
state.value = openTemplate(OpenTemplate.Params(ctx))
|
||||
state.value = openTemplate.asFlow(OpenTemplate.Params(ctx))
|
||||
.map { result ->
|
||||
when(result) {
|
||||
is Result.Failure -> {
|
||||
|
|
|
@ -7,7 +7,6 @@ import com.anytypeio.anytype.core_models.Hash
|
|||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.block.repo.BlockRepository
|
||||
import com.anytypeio.anytype.presentation.util.TEMPORARY_DIRECTORY_NAME
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
|
|
@ -3921,14 +3921,10 @@ open class EditorViewModelTest {
|
|||
|
||||
private fun stubGetDefaultObjectType(type: String? = null, name: String? = null) {
|
||||
getDefaultEditorType.stub {
|
||||
onBlocking { invoke(Unit) } doReturn flow {
|
||||
emit(
|
||||
GetDefaultEditorType.Response(
|
||||
type,
|
||||
name
|
||||
)
|
||||
)
|
||||
}
|
||||
onBlocking { run(Unit) } doReturn GetDefaultEditorType.Response(
|
||||
type,
|
||||
name
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ dependencies {
|
|||
implementation project(':core-ui')
|
||||
implementation project(':analytics')
|
||||
implementation project(':core-models')
|
||||
implementation project(':core-utils')
|
||||
|
||||
def applicationDependencies = rootProject.ext.mainApplication
|
||||
def unitTestDependencies = rootProject.ext.unitTesting
|
||||
|
|
|
@ -33,11 +33,17 @@ fun AccountAndDataScreen(
|
|||
onClearFileCachedClicked: () -> Unit,
|
||||
onDeleteAccountClicked: () -> Unit,
|
||||
onLogoutClicked: () -> Unit,
|
||||
onDebugSyncReportClicked: () -> Unit,
|
||||
isLogoutInProgress: Boolean,
|
||||
isClearCacheInProgress: Boolean
|
||||
isClearCacheInProgress: Boolean,
|
||||
isDebugSyncReportInProgress: Boolean,
|
||||
isShowDebug: Boolean,
|
||||
) {
|
||||
Column {
|
||||
Box(Modifier.padding(vertical = 6.dp).align(Alignment.CenterHorizontally)) {
|
||||
Box(
|
||||
Modifier
|
||||
.padding(vertical = 6.dp)
|
||||
.align(Alignment.CenterHorizontally)) {
|
||||
Dragger()
|
||||
}
|
||||
Toolbar(stringResource(R.string.account_and_data))
|
||||
|
@ -56,18 +62,27 @@ fun AccountAndDataScreen(
|
|||
isInProgress = isClearCacheInProgress
|
||||
)
|
||||
Divider()
|
||||
if (isShowDebug) {
|
||||
ActionWithProgressBar(
|
||||
name = stringResource(R.string.debug_sync_report),
|
||||
color = colorResource(R.color.text_primary),
|
||||
onClick = onDebugSyncReportClicked,
|
||||
isInProgress = isDebugSyncReportInProgress
|
||||
)
|
||||
Divider()
|
||||
}
|
||||
Section(stringResource(R.string.account))
|
||||
Action(
|
||||
name = stringResource(R.string.delete_account),
|
||||
color = colorResource(R.color.text_primary),
|
||||
onClick = onDeleteAccountClicked
|
||||
name = stringResource(R.string.delete_account),
|
||||
color = colorResource(R.color.text_primary),
|
||||
onClick = onDeleteAccountClicked
|
||||
)
|
||||
Divider()
|
||||
ActionWithProgressBar(
|
||||
name = stringResource(R.string.log_out),
|
||||
color = colorResource(R.color.palette_dark_red),
|
||||
onClick = onLogoutClicked,
|
||||
isInProgress = isLogoutInProgress
|
||||
name = stringResource(R.string.log_out),
|
||||
color = colorResource(R.color.palette_dark_red),
|
||||
onClick = onLogoutClicked,
|
||||
isInProgress = isLogoutInProgress
|
||||
)
|
||||
Divider()
|
||||
Box(Modifier.height(54.dp))
|
||||
|
@ -77,7 +92,9 @@ fun AccountAndDataScreen(
|
|||
@Composable
|
||||
fun Section(name: String) {
|
||||
Box(
|
||||
modifier = Modifier.height(52.dp).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.height(52.dp)
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.BottomStart
|
||||
) {
|
||||
Text(
|
||||
|
@ -98,7 +115,9 @@ fun Pincode(
|
|||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.height(52.dp).clickable(onClick = onClick)
|
||||
modifier = Modifier
|
||||
.height(52.dp)
|
||||
.clickable(onClick = onClick)
|
||||
) {
|
||||
Image(
|
||||
painterResource(R.drawable.ic_pin_code),
|
||||
|
@ -138,7 +157,10 @@ fun Action(
|
|||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.height(52.dp).fillMaxWidth().clickable(onClick = onClick),
|
||||
modifier = Modifier
|
||||
.height(52.dp)
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Text(
|
||||
|
@ -160,7 +182,10 @@ fun ActionWithProgressBar(
|
|||
isInProgress: Boolean
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.height(52.dp).fillMaxWidth().clickable(onClick = onClick),
|
||||
modifier = Modifier
|
||||
.height(52.dp)
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = onClick),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Text(
|
||||
|
@ -173,7 +198,10 @@ fun ActionWithProgressBar(
|
|||
)
|
||||
if (isInProgress) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.CenterEnd).padding(end = 20.dp).size(24.dp),
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(end = 20.dp)
|
||||
.size(24.dp),
|
||||
color = colorResource(R.color.shape_secondary)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.anytypeio.anytype.ui_settings.account
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
@ -9,7 +10,10 @@ import com.anytypeio.anytype.analytics.base.sendEvent
|
|||
import com.anytypeio.anytype.domain.account.DeleteAccount
|
||||
import com.anytypeio.anytype.domain.base.BaseUseCase
|
||||
import com.anytypeio.anytype.domain.base.Interactor
|
||||
import com.anytypeio.anytype.domain.base.fold
|
||||
import com.anytypeio.anytype.domain.device.ClearFileCache
|
||||
import com.anytypeio.anytype.ui_settings.account.repo.DebugSyncShareDownloader
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -17,14 +21,19 @@ import timber.log.Timber
|
|||
class AccountAndDataViewModel(
|
||||
private val clearFileCache: ClearFileCache,
|
||||
private val analytics: Analytics,
|
||||
private val deleteAccount: DeleteAccount
|
||||
private val deleteAccount: DeleteAccount,
|
||||
private val debugSyncShareDownloader: DebugSyncShareDownloader,
|
||||
) : ViewModel() {
|
||||
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
val isClearFileCacheInProgress = MutableStateFlow(false)
|
||||
val isDebugSyncReportInProgress = MutableStateFlow(false)
|
||||
val isLoggingOut = MutableStateFlow(false)
|
||||
val debugSyncReportUri = MutableStateFlow<Uri?>(null)
|
||||
|
||||
fun onClearFileCacheAccepted() {
|
||||
viewModelScope.launch {
|
||||
jobs += viewModelScope.launch {
|
||||
clearFileCache(BaseUseCase.None).collect { status ->
|
||||
when (status) {
|
||||
is Interactor.Status.Started -> {
|
||||
|
@ -32,7 +41,6 @@ class AccountAndDataViewModel(
|
|||
}
|
||||
is Interactor.Status.Error -> {
|
||||
isClearFileCacheInProgress.value = false
|
||||
val msg = "Error while clearing file cache: ${status.throwable.message}"
|
||||
Timber.e(status.throwable, "Error while clearing file cache")
|
||||
// TODO send toast
|
||||
}
|
||||
|
@ -49,14 +57,14 @@ class AccountAndDataViewModel(
|
|||
}
|
||||
|
||||
fun onClearCacheButtonClicked() {
|
||||
viewModelScope.sendEvent(
|
||||
jobs += viewModelScope.sendEvent(
|
||||
analytics = analytics,
|
||||
eventName = EventsDictionary.fileOffloadScreenShow
|
||||
)
|
||||
}
|
||||
|
||||
fun onDeleteAccountClicked() {
|
||||
viewModelScope.launch {
|
||||
jobs += viewModelScope.launch {
|
||||
deleteAccount(BaseUseCase.None).process(
|
||||
success = {
|
||||
sendEvent(
|
||||
|
@ -72,16 +80,45 @@ class AccountAndDataViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onDebugSyncReportClicked() {
|
||||
jobs += viewModelScope.launch {
|
||||
debugSyncShareDownloader.stream(Unit).collect { result ->
|
||||
result.fold(
|
||||
onSuccess = { report ->
|
||||
isDebugSyncReportInProgress.value = false
|
||||
debugSyncReportUri.value = report
|
||||
Timber.d(report.toString())
|
||||
},
|
||||
onLoading = { isDebugSyncReportInProgress.value = true },
|
||||
onFailure = { e ->
|
||||
isDebugSyncReportInProgress.value = false
|
||||
Timber.e(e, "Error while creating a debug sync report")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onStop() {
|
||||
Timber.d("onStop, ")
|
||||
jobs.apply {
|
||||
forEach { it.cancel() }
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val clearFileCache: ClearFileCache,
|
||||
private val deleteAccount: DeleteAccount,
|
||||
private val analytics: Analytics
|
||||
private val debugSyncShareDownloader: DebugSyncShareDownloader,
|
||||
private val analytics: Analytics,
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return AccountAndDataViewModel(
|
||||
clearFileCache = clearFileCache,
|
||||
deleteAccount = deleteAccount,
|
||||
debugSyncShareDownloader = debugSyncShareDownloader,
|
||||
analytics = analytics
|
||||
) as T
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package com.anytypeio.anytype.ui_settings.account.repo
|
||||
|
||||
import android.net.Uri
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.domain.debugging.DebugSync
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class DebugSyncShareDownloader(
|
||||
private val debugSync: DebugSync,
|
||||
private val fileSaver: FileSaver,
|
||||
) : ResultInteractor<Unit, Uri>() {
|
||||
|
||||
private fun getFileName(): String {
|
||||
val date = Calendar.getInstance().time
|
||||
val dateFormat = SimpleDateFormat("dd-MM-yyyy-HH:mm:ss", Locale.getDefault())
|
||||
val formattedDate = dateFormat.format(date)
|
||||
val fileName = "DebugSync$formattedDate.txt"
|
||||
return fileName
|
||||
}
|
||||
|
||||
override suspend fun doWork(params: Unit): Uri {
|
||||
val content = debugSync.run(Unit)
|
||||
return fileSaver.run(FileSaver.Params(content = content, name = getFileName()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package com.anytypeio.anytype.ui_settings.account.repo
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import com.anytypeio.anytype.domain.base.ResultInteractor
|
||||
import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class FileSaver(
|
||||
private val context: Context,
|
||||
private val uriFileProvider: UriFileProvider
|
||||
) : ResultInteractor<FileSaver.Params, Uri>() {
|
||||
|
||||
data class Params(
|
||||
val content: String,
|
||||
val name: String
|
||||
)
|
||||
|
||||
override suspend fun doWork(params: Params): Uri {
|
||||
val cacheDir = context.cacheDir
|
||||
|
||||
require(cacheDir != null) { "Impossible to cache files!" }
|
||||
|
||||
val downloadFolder = File("${cacheDir.path}/debug_sync/").apply { mkdirs() }
|
||||
|
||||
val resultFilePath = "${cacheDir.path}/${params.name}"
|
||||
val resultFile = File(resultFilePath)
|
||||
|
||||
if (!resultFile.exists()) {
|
||||
val tempFileFolderPath = "${downloadFolder.absolutePath}/tmp"
|
||||
val tempDir = File(tempFileFolderPath)
|
||||
if (tempDir.exists()) tempDir.deleteRecursively()
|
||||
tempDir.mkdirs()
|
||||
|
||||
val tempResult = File(tempFileFolderPath, params.name)
|
||||
FileOutputStream(tempResult).use {
|
||||
it.write(params.content.toByteArray())
|
||||
}
|
||||
|
||||
tempResult.renameTo(resultFile)
|
||||
}
|
||||
return uriFileProvider.getUriForFile(resultFile)
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@
|
|||
<string name="appearance">Appearance</string>
|
||||
<string name="recovery_phrase">Recovery phrase</string>
|
||||
<string name="clear_file_cache">Clear file cache</string>
|
||||
<string name="debug_sync_report">Debug Sync Report</string>
|
||||
<string name="account">Account</string>
|
||||
<string name="reset_account">Reset account</string>
|
||||
<string name="delete_account">Delete account</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue