From 2acfccb6e2bbdd0a444da7f95562b32507e8670d Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 30 Aug 2022 11:15:19 +0300 Subject: [PATCH] DROID-81 Editor | Enhancement | Share files (#2568) --- .../features/editor/base/EditorTestSetup.kt | 8 +- app/src/main/AndroidManifest.xml | 9 ++ .../anytypeio/anytype/di/feature/ArchiveDI.kt | 1 + .../anytypeio/anytype/di/feature/EditorDI.kt | 107 ++++++++++++------ .../providers/DefaultUriFileProvider.kt | 22 ++++ .../anytype/ui/editor/EditorFragment.kt | 31 ++--- app/src/main/res/xml/provider_paths.xml | 4 + .../anytypeio/anytype/core_models/Command.kt | 5 + .../anytype/data/auth/other/DataDownloader.kt | 4 +- .../auth/repo/block/BlockDataRepository.kt | 4 + .../data/auth/repo/block/BlockDataStore.kt | 1 + .../data/auth/repo/block/BlockRemote.kt | 1 + .../auth/repo/block/BlockRemoteDataStore.kt | 4 + .../anytype/device/base/AndroidDevice.kt | 4 +- .../domain/block/repo/BlockRepository.kt | 1 + .../middleware/block/BlockMiddleware.kt | 4 + .../middleware/interactor/Middleware.kt | 12 ++ .../middleware/service/MiddlewareService.kt | 3 + .../MiddlewareServiceImplementation.kt | 11 ++ .../presentation/editor/EditorViewModel.kt | 38 ++++++- .../editor/EditorViewModelFactory.kt | 4 +- .../presentation/editor/editor/Intent.kt | 9 ++ .../editor/editor/Orchestrator.kt | 17 +++ .../downloader/MiddlewareShareDownloader.kt | 55 +++++++++ .../util/downloader/UriFileProvider.kt | 10 ++ .../editor/EditorViewModelTest.kt | 97 +++++++++++++--- .../editor/EditorPresentationTestSetup.kt | 21 ++-- 27 files changed, 408 insertions(+), 79 deletions(-) create mode 100644 app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt create mode 100644 app/src/main/res/xml/provider_paths.xml create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt create mode 100644 presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/UriFileProvider.kt diff --git a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt index 484ad65b13..2dcc6cb485 100644 --- a/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt +++ b/app/src/androidTest/java/com/anytypeio/anytype/features/editor/base/EditorTestSetup.kt @@ -95,6 +95,8 @@ import com.anytypeio.anytype.presentation.editor.template.EditorTemplateDelegate import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher +import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader +import com.anytypeio.anytype.providers.DefaultUriFileProvider import com.anytypeio.anytype.test_utils.MockDataFactory import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.emptyFlow @@ -127,9 +129,12 @@ open class EditorTestSetup { lateinit var updateDetail: UpdateDetail lateinit var getCompatibleObjectTypes: GetCompatibleObjectTypes - @Mock + lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory + @Mock + lateinit var middlewareShareDownloader: MiddlewareShareDownloader + @Mock lateinit var openPage: OpenPage @Mock @@ -352,6 +357,7 @@ open class EditorTestSetup { duplicateBlock = duplicateBlock, updateAlignment = updateAlignment, downloadFile = downloadFile, + middlewareShareDownloader = middlewareShareDownloader, mergeBlocks = mergeBlocks, updateTextColor = updateTextColor, replaceBlock = replaceBlock, diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bace1654fb..713db6ee67 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,6 +48,15 @@ android:screenOrientation="fullSensor" android:exported="false" tools:replace="screenOrientation" /> + + + \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/ArchiveDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/ArchiveDI.kt index fe3969ada9..6641d0ec41 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/ArchiveDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/ArchiveDI.kt @@ -20,6 +20,7 @@ import dagger.Subcomponent modules = [ ArchiveModule::class, EditorUseCaseModule::class, + EditorUseCaseModule.Bindings::class, EditorSessionModule::class ] ) diff --git a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt index 24f006d601..e4ddd534e0 100644 --- a/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt +++ b/app/src/main/java/com/anytypeio/anytype/di/feature/EditorDI.kt @@ -48,8 +48,6 @@ import com.anytypeio.anytype.domain.clipboard.Paste import com.anytypeio.anytype.domain.config.UserSettingsRepository import com.anytypeio.anytype.domain.cover.SetDocCoverImage import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes -import com.anytypeio.anytype.domain.search.SearchObjects -import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.download.DownloadFile import com.anytypeio.anytype.domain.download.Downloader import com.anytypeio.anytype.domain.event.interactor.EventChannel @@ -72,6 +70,8 @@ import com.anytypeio.anytype.domain.page.UpdateTitle import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark import com.anytypeio.anytype.domain.relations.AddFileToObject +import com.anytypeio.anytype.domain.relations.SetRelationKey +import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.status.ThreadStatusChannel @@ -108,14 +108,24 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvide import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher +import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader +import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider +import com.anytypeio.anytype.providers.DefaultUriFileProvider import com.anytypeio.anytype.ui.editor.EditorFragment +import dagger.Binds import dagger.Module import dagger.Provides import dagger.Subcomponent import kotlinx.coroutines.Dispatchers -@Subcomponent(modules = [EditorSessionModule::class, EditorUseCaseModule::class]) +@Subcomponent( + modules = [ + EditorSessionModule::class, + EditorUseCaseModule::class, + EditorUseCaseModule.Bindings::class + ] +) @PerScreen interface EditorSubComponent { @@ -134,23 +144,23 @@ interface EditorSubComponent { // Relations fun documentRelationSubComponent(): DocumentRelationSubComponent.Builder - fun relationAddToObjectComponent() : RelationAddToObjectSubComponent.Builder - fun relationCreateFromScratchForObjectComponent() : RelationCreateFromScratchForObjectSubComponent.Builder - fun relationCreateFromScratchForObjectBlockComponent() : RelationCreateFromScratchForObjectBlockSubComponent.Builder + fun relationAddToObjectComponent(): RelationAddToObjectSubComponent.Builder + fun relationCreateFromScratchForObjectComponent(): RelationCreateFromScratchForObjectSubComponent.Builder + fun relationCreateFromScratchForObjectBlockComponent(): RelationCreateFromScratchForObjectBlockSubComponent.Builder fun relationTextValueComponent(): RelationTextValueSubComponent.Builder - fun editDocRelationComponent() : ObjectObjectRelationValueSubComponent.Builder + fun editDocRelationComponent(): ObjectObjectRelationValueSubComponent.Builder fun editRelationDateComponent(): RelationDataValueSubComponent.Builder - fun objectCoverComponent() : SelectCoverObjectSubComponent.Builder - fun objectUnsplashComponent() : UnsplashSubComponent.Builder - fun objectMenuComponent() : ObjectMenuComponent.Builder + fun objectCoverComponent(): SelectCoverObjectSubComponent.Builder + fun objectUnsplashComponent(): UnsplashSubComponent.Builder + fun objectMenuComponent(): ObjectMenuComponent.Builder - fun objectLayoutComponent() : ObjectLayoutSubComponent.Builder - fun objectAppearanceSettingComponent() : ObjectAppearanceSettingSubComponent.Builder - fun objectAppearanceIconComponent() : ObjectAppearanceIconSubComponent.Builder - fun objectAppearancePreviewLayoutComponent() : ObjectAppearancePreviewLayoutSubComponent.Builder - fun objectAppearanceCoverComponent() : ObjectAppearanceCoverSubComponent.Builder - fun objectAppearanceChooseDescription() : ObjectAppearanceChooseDescriptionSubComponent.Builder + fun objectLayoutComponent(): ObjectLayoutSubComponent.Builder + fun objectAppearanceSettingComponent(): ObjectAppearanceSettingSubComponent.Builder + fun objectAppearanceIconComponent(): ObjectAppearanceIconSubComponent.Builder + fun objectAppearancePreviewLayoutComponent(): ObjectAppearancePreviewLayoutSubComponent.Builder + fun objectAppearanceCoverComponent(): ObjectAppearanceCoverSubComponent.Builder + fun objectAppearanceChooseDescription(): ObjectAppearanceChooseDescriptionSubComponent.Builder fun setBlockTextValueComponent(): SetBlockTextValueSubComponent.Builder } @@ -253,7 +263,7 @@ object EditorSessionModule { getDefaultEditorType: GetDefaultEditorType, getTemplates: GetTemplates, createPage: CreatePage, - ) : CreateNewObject = CreateNewObject( + ): CreateNewObject = CreateNewObject( getDefaultEditorType, getTemplates, createPage @@ -265,7 +275,7 @@ object EditorSessionModule { fun provideTemplateDelegate( getTemplates: GetTemplates, applyTemplate: ApplyTemplate - ) : EditorTemplateDelegate = DefaultEditorTemplateDelegate( + ): EditorTemplateDelegate = DefaultEditorTemplateDelegate( getTemplates = getTemplates, applyTemplate = applyTemplate ) @@ -274,7 +284,7 @@ object EditorSessionModule { @Provides @PerScreen fun provideSimpleTableDelegate( - ) : SimpleTableDelegate = DefaultSimpleTableDelegate() + ): SimpleTableDelegate = DefaultSimpleTableDelegate() @JvmStatic @Provides @@ -294,7 +304,8 @@ object EditorSessionModule { @JvmStatic @Provides - fun provideDocumentExternalEventReducer(): DocumentExternalEventReducer = DocumentExternalEventReducer() + fun provideDocumentExternalEventReducer(): DocumentExternalEventReducer = + DocumentExternalEventReducer() @JvmStatic @Provides @@ -349,7 +360,8 @@ object EditorSessionModule { redo: Redo, setRelationKey: SetRelationKey, analytics: Analytics, - updateBlocksMark: UpdateBlocksMark + updateBlocksMark: UpdateBlocksMark, + middlewareShareDownloader: MiddlewareShareDownloader ): Orchestrator = Orchestrator( stores = storage, createBlock = createBlock, @@ -369,6 +381,7 @@ object EditorSessionModule { updateDivider = updateDivider, memory = memory, downloadFile = downloadFile, + middlewareShareDownloader = middlewareShareDownloader, turnIntoDocument = turnIntoDocument, textInteractor = Interactor.TextInteractor( proxies = proxer, @@ -389,7 +402,7 @@ object EditorSessionModule { updateBlocksMark = updateBlocksMark, setObjectType = setObjectType, createTable = createTable, - fillTableRow = fillTableRow + fillTableRow = fillTableRow, ) } @@ -790,21 +803,21 @@ object EditorUseCaseModule { @PerScreen fun provideDefaultObjectRelationProvider( storage: Editor.Storage - ) : ObjectRelationProvider = DefaultObjectRelationProvider(storage.relations) + ): ObjectRelationProvider = DefaultObjectRelationProvider(storage.relations) @JvmStatic @Provides @PerScreen fun provideDefaultObjectValueProvider( storage: Editor.Storage - ) : ObjectValueProvider = DefaultObjectValueProvider(storage.details) + ): ObjectValueProvider = DefaultObjectValueProvider(storage.details) @JvmStatic @Provides @PerScreen fun provideObjectTypeProvider( storage: Editor.Storage - ) : ObjectTypeProvider = object : ObjectTypeProvider { + ): ObjectTypeProvider = object : ObjectTypeProvider { override fun provide(): List = storage.objectTypes.current() } @@ -813,26 +826,26 @@ object EditorUseCaseModule { @PerScreen fun provideObjectDetailProvider( storage: Editor.Storage - ) : ObjectDetailProvider = object : ObjectDetailProvider { + ): ObjectDetailProvider = object : ObjectDetailProvider { override fun provide(): Map = storage.details.current().details } @JvmStatic @Provides @PerScreen - fun providePayloadDispatcher() : Dispatcher = Dispatcher.Default() + fun providePayloadDispatcher(): Dispatcher = Dispatcher.Default() @JvmStatic @Provides @PerScreen - fun provideDelegator() : Delegator = Delegator.Default() + fun provideDelegator(): Delegator = Delegator.Default() @JvmStatic @Provides @PerScreen fun provideDetailManager( storage: Editor.Storage - ) : DetailModificationManager = InternalDetailModificationManager( + ): DetailModificationManager = InternalDetailModificationManager( store = storage.details ) @@ -846,14 +859,14 @@ object EditorUseCaseModule { @PerScreen fun provideUpdateDetailUseCase( repository: BlockRepository - ) : UpdateDetail = UpdateDetail(repository) + ): UpdateDetail = UpdateDetail(repository) @JvmStatic @Provides @PerScreen fun provideGetObjectTypesUseCase( repository: BlockRepository - ) : GetObjectTypes = GetObjectTypes(repository) + ): GetObjectTypes = GetObjectTypes(repository) @JvmStatic @Provides @@ -934,7 +947,7 @@ object EditorUseCaseModule { @JvmStatic @Provides @PerScreen - fun getTemplates(repo: BlockRepository) : GetTemplates = GetTemplates( + fun getTemplates(repo: BlockRepository): GetTemplates = GetTemplates( repo = repo, dispatchers = AppCoroutineDispatchers( io = Dispatchers.IO, @@ -946,7 +959,7 @@ object EditorUseCaseModule { @JvmStatic @Provides @PerScreen - fun applyTemplates(repo: BlockRepository) : ApplyTemplate = ApplyTemplate( + fun applyTemplates(repo: BlockRepository): ApplyTemplate = ApplyTemplate( repo = repo, dispatchers = AppCoroutineDispatchers( io = Dispatchers.IO, @@ -954,4 +967,32 @@ object EditorUseCaseModule { main = Dispatchers.Main ) ) + + @JvmStatic + @Provides + @PerScreen + fun providesMiddlewareShareDownloader( + repo: BlockRepository, + context: Context, + fileProvider: UriFileProvider + ): MiddlewareShareDownloader = MiddlewareShareDownloader( + repo = repo, + dispatchers = AppCoroutineDispatchers( + io = Dispatchers.IO, + computation = Dispatchers.Default, + main = Dispatchers.Main + ), + context = context.applicationContext, + uriFileProvider = fileProvider + ) + + @Module + interface Bindings { + + @PerScreen + @Binds + fun bindUriFileProvider( + defaultProvider: DefaultUriFileProvider + ): UriFileProvider + } } \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt b/app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt new file mode 100644 index 0000000000..23ebf32276 --- /dev/null +++ b/app/src/main/java/com/anytypeio/anytype/providers/DefaultUriFileProvider.kt @@ -0,0 +1,22 @@ +package com.anytypeio.anytype.providers + +import android.content.Context +import android.net.Uri +import androidx.core.content.FileProvider +import com.anytypeio.anytype.BuildConfig +import com.anytypeio.anytype.presentation.util.downloader.UriFileProvider +import java.io.File +import javax.inject.Inject + +class DefaultUriFileProvider @Inject constructor( + private val context: Context +) : UriFileProvider { + + override fun getUriForFile(file: File): Uri = FileProvider.getUriForFile( + context, + BuildConfig.APPLICATION_ID + PROVIDER, + file + ) +} + +private const val PROVIDER = ".provider" \ No newline at end of file diff --git a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt index eda280354b..1d9473d9fe 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/editor/EditorFragment.kt @@ -2,9 +2,13 @@ package com.anytypeio.anytype.ui.editor import android.animation.ObjectAnimator import android.app.Activity +import android.app.DownloadManager import android.content.ActivityNotFoundException +import android.content.BroadcastReceiver import android.content.ClipData +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.graphics.Point import android.net.Uri import android.os.Build @@ -22,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart +import androidx.core.content.ContextCompat.getSystemService import androidx.core.os.bundleOf import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP @@ -1104,7 +1109,11 @@ open class EditorFragment : NavigationFragment(R.layout.f is Command.ShowKeyboard -> { binding.recycler.findFocus()?.focusAndShowKeyboard() } - is Command.OpenFileByDefaultApp -> openFileByDefaultApp(command) + is Command.OpenFileByDefaultApp -> { + vm.startSharingFile(command.id) { uri -> + openFileByDefaultApp(uri) + } + } Command.ShowTextLinkMenu -> { val urlButton = binding.markupToolbar.findViewById(R.id.url) val popup = TextLinkPopupMenu( @@ -1149,22 +1158,18 @@ open class EditorFragment : NavigationFragment(R.layout.f } } - private fun openFileByDefaultApp(command: Command.OpenFileByDefaultApp) { + private fun openFileByDefaultApp(uri: Uri) { try { - val uri = Uri.parse(command.uri) - val intent = Intent().apply { - action = Intent.ACTION_VIEW - if (command.mime.isNotEmpty()) { - setDataAndTypeAndNormalize(uri, command.mime) - } else { - data = uri + val intent = Intent(Intent.ACTION_VIEW, uri) + .apply { + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - startActivity(intent) + startActivity( + intent + ) } catch (e: Exception) { if (e is ActivityNotFoundException) { - toast("No Application found to open the selected file") + toast("No application found to open the selected file") } else { toast("Could not open file: ${e.message}") } diff --git a/app/src/main/res/xml/provider_paths.xml b/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 0000000000..4419d669d4 --- /dev/null +++ b/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt index fcf6d9cfc5..be5d00a429 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt @@ -7,6 +7,11 @@ sealed class Command { val type: Block.Content.File.Type? ) + class DownloadFile( + val path: String, + val hash: Hash + ) + /** * Command for turning simple blocks into documents * @property context id of the context diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/other/DataDownloader.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/other/DataDownloader.kt index c31c7cf64e..9b5afdc707 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/other/DataDownloader.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/other/DataDownloader.kt @@ -5,7 +5,5 @@ import com.anytypeio.anytype.domain.download.Downloader class DataDownloader(private val device: Device) : Downloader { - override fun download(url: Url, name: String) { - device.download(url, name) - } + override fun download(url: Url, name: String) = device.download(url, name) } \ No newline at end of file diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index 40152c637a..0b04391791 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -256,6 +256,10 @@ class BlockDataRepository( command: Command.UploadFile ): Hash = remote.uploadFile(command) + override suspend fun downloadFile( + command: Command.DownloadFile + ): String = remote.downloadFile(command) + override suspend fun getObjectInfoWithLinks( pageId: String ): ObjectInfoWithLinks = remote.getObjectInfoWithLinks(pageId) diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt index 6f15d88b41..5587eeffbb 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataStore.kt @@ -77,6 +77,7 @@ interface BlockDataStore { suspend fun setRelationKey(command: Command.SetRelationKey): Payload suspend fun uploadFile(command: Command.UploadFile): String + suspend fun downloadFile(command: Command.DownloadFile): String suspend fun getObjectInfoWithLinks(pageId: String): ObjectInfoWithLinks diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index be1edf7088..1ca7a1defc 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -75,6 +75,7 @@ interface BlockRemote { suspend fun copy(command: Command.Copy) : Response.Clipboard.Copy suspend fun uploadFile(command: Command.UploadFile): String + suspend fun downloadFile(command: Command.DownloadFile): String suspend fun getObjectInfoWithLinks(pageId: String): ObjectInfoWithLinks diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt index 4a7515721c..6c930fceb7 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemoteDataStore.kt @@ -198,6 +198,10 @@ class BlockRemoteDataStore(private val remote: BlockRemote) : BlockDataStore { command: Command.UploadFile ): String = remote.uploadFile(command) + override suspend fun downloadFile( + command: Command.DownloadFile + ): String = remote.downloadFile(command) + override suspend fun getObjectInfoWithLinks(pageId: String): ObjectInfoWithLinks = remote.getObjectInfoWithLinks(pageId) diff --git a/device/src/main/java/com/anytypeio/anytype/device/base/AndroidDevice.kt b/device/src/main/java/com/anytypeio/anytype/device/base/AndroidDevice.kt index 5f105385f9..7f2a94992c 100644 --- a/device/src/main/java/com/anytypeio/anytype/device/base/AndroidDevice.kt +++ b/device/src/main/java/com/anytypeio/anytype/device/base/AndroidDevice.kt @@ -6,7 +6,5 @@ import com.anytypeio.anytype.device.download.AndroidDeviceDownloader class AndroidDevice( private val downloader: AndroidDeviceDownloader ) : Device { - override fun download(url: String, name: String) { - downloader.download(url = url, name = name) - } + override fun download(url: String, name: String) = downloader.download(url = url, name = name) } \ No newline at end of file diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index b90e8e7156..62f62a24b9 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -28,6 +28,7 @@ import com.anytypeio.anytype.domain.page.Undo interface BlockRepository { suspend fun uploadFile(command: Command.UploadFile): Hash + suspend fun downloadFile(command: Command.DownloadFile): String suspend fun move(command: Command.Move): Payload suspend fun unlink(command: Command.Unlink): Payload diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index 62a554b00d..ad70eeac99 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -225,6 +225,10 @@ class BlockMiddleware( command: Command.UploadFile ): String = middleware.fileUpload(command).hash + override suspend fun downloadFile( + command: Command.DownloadFile + ): String = middleware.fileDownload(command).localPath + override suspend fun getObjectInfoWithLinks(pageId: String): ObjectInfoWithLinks { return middleware.navigationGetObjectInfoWithLinks(pageId).toCoreModel() } diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 7be7f32ccd..7f70818ef9 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -911,6 +911,18 @@ class Middleware( return Response.Media.Upload(response.hash) } + @Throws(Exception::class) + fun fileDownload(command: Command.DownloadFile): Rpc.File.Download.Response { + val request = Rpc.File.Download.Request( + hash = command.hash, + path = command.path + ) + if (BuildConfig.DEBUG) logRequest(request) + val response = service.fileDownload(request) + if (BuildConfig.DEBUG) logResponse(response) + return response + } + @Throws(Exception::class) fun getConfig(): Config { TODO() diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index 82eb0b7a45..a75750dd94 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -156,6 +156,9 @@ interface MiddlewareService { @Throws(Exception::class) fun fileUpload(request: Rpc.File.Upload.Request): Rpc.File.Upload.Response + @Throws(Exception::class) + fun fileDownload(request: Rpc.File.Download.Request): Rpc.File.Download.Response + //endregion //region UNSPLASH commands diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 827ca1dc62..322eb93fc6 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -583,6 +583,17 @@ class MiddlewareServiceImplementation : MiddlewareService { } } + override fun fileDownload(request: Rpc.File.Download.Request): Rpc.File.Download.Response { + val encoded = Service.fileDownload(Rpc.File.Download.Request.ADAPTER.encode(request)) + val response = Rpc.File.Download.Response.ADAPTER.decode(encoded) + val error = response.error + if (error != null && error.code != Rpc.File.Download.Response.Error.Code.NULL) { + throw Exception(error.description) + } else { + return response + } + } + override fun navigationListObjects(request: Rpc.Navigation.ListObjects.Request): Rpc.Navigation.ListObjects.Response { val encoded = Service.navigationListObjects(Rpc.Navigation.ListObjects.Request.ADAPTER.encode(request)) diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt index 0ff3ea46da..3dbad8cddf 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModel.kt @@ -194,6 +194,7 @@ 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.downloader.MiddlewareShareDownloader import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow @@ -3882,6 +3883,31 @@ class EditorViewModel( } } + fun startSharingFile(id: String, onDownloaded: (Uri) -> Unit = {}) { + + Timber.d("startDownloadingFile, id:[$id]") + + sendToast("Preparing file to share...") + + val block = blocks.firstOrNull { it.id == id } + val content = block?.content + + if (content is Content.File && content.state == Content.File.State.DONE) { + viewModelScope.launch { + orchestrator.proxies.intents.send( + Media.ShareFile( + hash = content.hash.orEmpty(), + name = content.name.orEmpty(), + type = content.type, + onDownloaded = onDownloaded + ) + ) + } + } else { + Timber.e("Block is not File or with wrong state, can't proceed with share!") + } + } + fun startDownloadingFile(id: String) { Timber.d("startDownloadingFile, id:[$id]") @@ -4524,11 +4550,15 @@ class EditorViewModel( } } - private fun getObjectTypes(excluded: List = emptyList(), action: (List) -> Unit) { + private fun getObjectTypes( + excluded: List = emptyList(), + action: (List) -> Unit + ) { viewModelScope.launch { getCompatibleObjectTypes.invoke( GetCompatibleObjectTypes.Params( - smartBlockType = blocks.first { it.id == context }.content().type, + smartBlockType = blocks.first { it.id == context } + .content().type, excludedTypes = excluded ) ).proceed( @@ -5115,7 +5145,9 @@ class EditorViewModel( when (val content = selected.content) { is Content.Bookmark -> { val target = content.targetObjectId - if (target != null) { proceedWithOpeningPage(target) } + if (target != null) { + proceedWithOpeningPage(target) + } } else -> sendToast("Unexpected object") } diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt index 462c218c33..8aeb301aed 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/EditorViewModelFactory.kt @@ -13,7 +13,6 @@ import com.anytypeio.anytype.domain.block.interactor.UpdateLinkMarks import com.anytypeio.anytype.domain.block.interactor.sets.CreateObjectSet import com.anytypeio.anytype.domain.cover.SetDocCoverImage import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes -import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon import com.anytypeio.anytype.domain.launch.GetDefaultEditorType @@ -21,15 +20,16 @@ import com.anytypeio.anytype.domain.misc.UrlBuilder import com.anytypeio.anytype.domain.page.CloseBlock import com.anytypeio.anytype.domain.page.CreateDocument import com.anytypeio.anytype.domain.page.CreateNewDocument +import com.anytypeio.anytype.domain.page.CreateNewObject import com.anytypeio.anytype.domain.page.CreateObject import com.anytypeio.anytype.domain.page.OpenPage +import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType import com.anytypeio.anytype.domain.status.InterceptThreadStatus import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.presentation.common.Action import com.anytypeio.anytype.presentation.common.Delegator import com.anytypeio.anytype.presentation.common.StateReducer -import com.anytypeio.anytype.domain.page.CreateNewObject import com.anytypeio.anytype.presentation.editor.editor.DetailModificationManager import com.anytypeio.anytype.presentation.editor.editor.Orchestrator import com.anytypeio.anytype.presentation.editor.editor.table.SimpleTableDelegate diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt index 5a1179f212..40210d5bdf 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Intent.kt @@ -1,7 +1,9 @@ package com.anytypeio.anytype.presentation.editor.editor +import android.net.Uri import android.os.Parcelable import com.anytypeio.anytype.core_models.Block +import com.anytypeio.anytype.core_models.Hash import com.anytypeio.anytype.core_models.Id import com.anytypeio.anytype.core_models.Position import com.anytypeio.anytype.core_utils.ext.Mimetype @@ -174,6 +176,13 @@ sealed class Intent { val type: Block.Content.File.Type? ) : Media() + class ShareFile( + val hash: Hash, + val name: String, + val type: Block.Content.File.Type?, + val onDownloaded: (Uri) -> Unit + ) : Media() + class Upload( val context: Id, val description: Description, diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt index 7e6bedb7f1..af7602feb0 100644 --- a/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/editor/editor/Orchestrator.kt @@ -35,6 +35,7 @@ import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark import com.anytypeio.anytype.domain.table.CreateTable import com.anytypeio.anytype.domain.table.FillTableRow +import com.anytypeio.anytype.presentation.dashboard.HomeDashboardStateMachine import com.anytypeio.anytype.presentation.editor.Editor import com.anytypeio.anytype.presentation.extension.sendAnalyticsChangeTextBlockStyleEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsCopyBlockEvent @@ -48,6 +49,7 @@ import com.anytypeio.anytype.presentation.extension.sendAnalyticsReorderBlockEve import com.anytypeio.anytype.presentation.extension.sendAnalyticsSplitBlockEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsUndoEvent import com.anytypeio.anytype.presentation.extension.sendAnalyticsUploadMediaEvent +import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader import timber.log.Timber class Orchestrator( @@ -64,6 +66,7 @@ class Orchestrator( private val turnIntoStyle: TurnIntoStyle, private val updateCheckbox: UpdateCheckbox, private val downloadFile: DownloadFile, + private val middlewareShareDownloader: MiddlewareShareDownloader, val updateText: UpdateText, private val updateAlignment: UpdateAlignment, private val uploadBlock: UploadBlock, @@ -432,6 +435,20 @@ class Orchestrator( success = { analytics.sendAnalyticsDownloadMediaEvent(intent.type) } ) } + is Intent.Media.ShareFile -> { + middlewareShareDownloader.execute( + params = MiddlewareShareDownloader.Params( + hash = intent.hash, + name = intent.name + ) + ).fold( + onSuccess = { uri -> + intent.onDownloaded(uri) + analytics.sendAnalyticsDownloadMediaEvent(intent.type) + }, + onFailure = { e -> Timber.e(e, "Error while sharing a file") } + ) + } is Intent.Media.Upload -> { uploadBlock( params = UploadBlock.Params( diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt new file mode 100644 index 0000000000..f0148052f8 --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/MiddlewareShareDownloader.kt @@ -0,0 +1,55 @@ +package com.anytypeio.anytype.presentation.util.downloader + +import android.content.Context +import android.net.Uri +import com.anytypeio.anytype.core_models.Command +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 + +class MiddlewareShareDownloader( + private val repo: BlockRepository, + private val dispatchers: AppCoroutineDispatchers, + private val context: Context, + private val uriFileProvider: UriFileProvider +) : ResultInteractor() { + + data class Params( + val hash: Hash, + val name: String + ) + + override suspend fun doWork(params: Params) = withContext(dispatchers.io) { + val cacheDir = context.cacheDir + + require(cacheDir != null) { "Impossible to cache files!" } + + val downloadFolder = File("${cacheDir.path}/${params.hash}").apply { mkdirs() } + + val resultFilePath = "${cacheDir.path}/${params.hash}/${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( + repo.downloadFile( + Command.DownloadFile( + hash = params.hash, + path = tempFileFolderPath + ) + ) + ) + + tempResult.renameTo(resultFile) + } + uriFileProvider.getUriForFile(resultFile) + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/UriFileProvider.kt b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/UriFileProvider.kt new file mode 100644 index 0000000000..c57ba5c0aa --- /dev/null +++ b/presentation/src/main/java/com/anytypeio/anytype/presentation/util/downloader/UriFileProvider.kt @@ -0,0 +1,10 @@ +package com.anytypeio.anytype.presentation.util.downloader + +import android.net.Uri +import java.io.File + +interface UriFileProvider { + + fun getUriForFile(file: File): Uri + +} \ No newline at end of file diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt index a53e29db88..857b27d5ba 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/EditorViewModelTest.kt @@ -1,5 +1,6 @@ package com.anytypeio.anytype.presentation.editor +import android.net.Uri import android.os.Build import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.anytypeio.anytype.analytics.base.Analytics @@ -13,7 +14,9 @@ import com.anytypeio.anytype.core_models.SmartBlockType import com.anytypeio.anytype.core_models.StubFile import com.anytypeio.anytype.core_models.StubNumbered import com.anytypeio.anytype.core_models.StubParagraph +import com.anytypeio.anytype.core_models.ThemeColor import com.anytypeio.anytype.core_models.ext.content +import com.anytypeio.anytype.core_models.ext.parseThemeTextColor import com.anytypeio.anytype.core_models.restrictions.ObjectRestriction import com.anytypeio.anytype.core_utils.common.EventWrapper import com.anytypeio.anytype.core_utils.ext.Mimetype @@ -50,8 +53,6 @@ import com.anytypeio.anytype.domain.clipboard.Paste import com.anytypeio.anytype.domain.config.Gateway import com.anytypeio.anytype.domain.cover.SetDocCoverImage import com.anytypeio.anytype.domain.dataview.interactor.GetCompatibleObjectTypes -import com.anytypeio.anytype.domain.search.SearchObjects -import com.anytypeio.anytype.domain.relations.SetRelationKey import com.anytypeio.anytype.domain.download.DownloadFile import com.anytypeio.anytype.domain.event.interactor.InterceptEvents import com.anytypeio.anytype.domain.icon.SetDocumentImageIcon @@ -68,12 +69,14 @@ import com.anytypeio.anytype.domain.page.Undo import com.anytypeio.anytype.domain.page.UpdateTitle import com.anytypeio.anytype.domain.page.bookmark.CreateBookmarkBlock import com.anytypeio.anytype.domain.page.bookmark.SetupBookmark +import com.anytypeio.anytype.domain.relations.SetRelationKey +import com.anytypeio.anytype.domain.search.SearchObjects import com.anytypeio.anytype.domain.sets.FindObjectSetForType import com.anytypeio.anytype.domain.status.InterceptThreadStatus -import com.anytypeio.anytype.domain.templates.ApplyTemplate -import com.anytypeio.anytype.domain.templates.GetTemplates import com.anytypeio.anytype.domain.table.CreateTable import com.anytypeio.anytype.domain.table.FillTableRow +import com.anytypeio.anytype.domain.templates.ApplyTemplate +import com.anytypeio.anytype.domain.templates.GetTemplates import com.anytypeio.anytype.domain.unsplash.DownloadUnsplashImage import com.anytypeio.anytype.domain.unsplash.UnsplashRepository import com.anytypeio.anytype.presentation.BuildConfig @@ -87,8 +90,6 @@ import com.anytypeio.anytype.presentation.editor.editor.Interactor import com.anytypeio.anytype.presentation.editor.editor.InternalDetailModificationManager import com.anytypeio.anytype.presentation.editor.editor.Markup import com.anytypeio.anytype.presentation.editor.editor.Orchestrator -import com.anytypeio.anytype.core_models.ThemeColor -import com.anytypeio.anytype.core_models.ext.parseThemeTextColor import com.anytypeio.anytype.presentation.editor.editor.ViewState import com.anytypeio.anytype.presentation.editor.editor.actions.ActionItemType import com.anytypeio.anytype.presentation.editor.editor.control.ControlPanelState @@ -111,6 +112,7 @@ import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.CoroutinesTestRule import com.anytypeio.anytype.presentation.util.Dispatcher import com.anytypeio.anytype.presentation.util.TXT +import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader import com.anytypeio.anytype.test_utils.MockDataFactory import com.anytypeio.anytype.test_utils.ValueClassAnswer import com.jraska.livedata.test @@ -121,6 +123,7 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Rule import org.junit.Test @@ -132,6 +135,7 @@ import org.mockito.kotlin.argThat import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq +import org.mockito.kotlin.given import org.mockito.kotlin.never import org.mockito.kotlin.stub import org.mockito.kotlin.times @@ -223,6 +227,9 @@ open class EditorViewModelTest { @Mock lateinit var downloadFile: DownloadFile + @Mock + lateinit var middlewareShareDownloader: MiddlewareShareDownloader + @Mock lateinit var uploadBlock: UploadBlock @@ -2616,6 +2623,63 @@ open class EditorViewModelTest { } } + @Test + fun `should start sharing a file`() { + + val root = MockDataFactory.randomUuid() + val file = MockBlockFactory.makeFileBlock() + val title = MockBlockFactory.makeTitleBlock() + + val page = listOf( + Block( + id = root, + fields = Block.Fields(emptyMap()), + content = Block.Content.Smart(), + children = listOf(title.id, file.id) + ), + title, + file + ) + + val flow: Flow> = flow { + delay(100) + emit( + listOf( + Event.Command.ShowObject( + root = root, + blocks = page, + context = root + ) + ) + ) + } + + stubObserveEvents(flow) + stubOpenPage() + givenViewModel(builder) + + givenSharedFile() + + vm.onStart(root) + + coroutineTestRule.advanceTime(100) + + // TESTING + + vm.startSharingFile(id = file.id) + + runTest { + verify(middlewareShareDownloader, times(1)).execute( + params = eq( + MiddlewareShareDownloader.Params( + name = file.content().name.orEmpty(), + hash = file.content().hash.orEmpty(), + ) + ) + ) + } + } + @Test fun `should start downloading file`() { @@ -3817,6 +3881,12 @@ open class EditorViewModelTest { } } + private fun givenSharedFile() { + middlewareShareDownloader.stub { + onBlocking { execute(any()) } doAnswer ValueClassAnswer(Uri.EMPTY) + } + } + private fun stubUpdateTextColor(root: String) { updateTextColor.stub { onBlocking { invoke(any()) } doReturn Either.Right( @@ -3878,7 +3948,9 @@ open class EditorViewModelTest { vm = EditorViewModel( openPage = openPage, closePage = closePage, + createDocument = createDocument, createObject = createObject, + createNewDocument = createNewDocument, interceptEvents = interceptEvents, interceptThreadStatus = interceptThreadStatus, updateLinkMarks = updateLinkMark, @@ -3890,16 +3962,13 @@ open class EditorViewModelTest { toggleStateHolder = ToggleStateHolder.Default(), coverImageHashProvider = coverImageHashProvider ), - createDocument = createDocument, - createNewDocument = createNewDocument, - analytics = analytics, - getDefaultEditorType = getDefaultEditorType, orchestrator = Orchestrator( createBlock = createBlock, replaceBlock = replaceBlock, updateTextColor = updateTextColor, duplicateBlock = duplicateBlock, downloadFile = downloadFile, + middlewareShareDownloader = middlewareShareDownloader, undo = undo, redo = redo, updateText = updateText, @@ -3935,22 +4004,24 @@ open class EditorViewModelTest { createTable = createTable, fillTableRow = fillTableRow ), + analytics = analytics, dispatcher = Dispatcher.Default(), + delegator = delegator, detailModificationManager = InternalDetailModificationManager(storage.details), updateDetail = updateDetail, getCompatibleObjectTypes = getCompatibleObjectTypes, objectTypesProvider = objectTypesProvider, searchObjects = searchObjects, + getDefaultEditorType = getDefaultEditorType, findObjectSetForType = findObjectSetForType, createObjectSet = createObjectSet, copyFileToCache = copyFileToCacheDirectory, downloadUnsplashImage = downloadUnsplashImage, setDocCoverImage = setDocCoverImage, setDocImageIcon = setDocImageIcon, - delegator = delegator, templateDelegate = editorTemplateDelegate, - createNewObject = createNewObject, - simpleTableDelegate = simpleTableDelegate + simpleTableDelegate = simpleTableDelegate, + createNewObject = createNewObject ) } diff --git a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt index 3f69a52440..3d4598d57a 100644 --- a/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt +++ b/presentation/src/test/java/com/anytypeio/anytype/presentation/editor/editor/EditorPresentationTestSetup.kt @@ -83,6 +83,7 @@ import com.anytypeio.anytype.presentation.editor.template.EditorTemplateDelegate import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory import com.anytypeio.anytype.presentation.util.Dispatcher +import com.anytypeio.anytype.presentation.util.downloader.MiddlewareShareDownloader import com.anytypeio.anytype.test_utils.MockDataFactory import com.anytypeio.anytype.test_utils.ValueClassAnswer import kotlinx.coroutines.flow.Flow @@ -164,6 +165,9 @@ open class EditorPresentationTestSetup { @Mock lateinit var downloadFile: DownloadFile + @Mock + lateinit var middlewareShareDownloader: MiddlewareShareDownloader + @Mock lateinit var uploadBlock: UploadBlock @@ -298,6 +302,7 @@ open class EditorPresentationTestSetup { updateTextColor = updateTextColor, duplicateBlock = duplicateBlock, downloadFile = downloadFile, + middlewareShareDownloader = middlewareShareDownloader, undo = undo, redo = redo, updateText = updateText, @@ -337,11 +342,13 @@ open class EditorPresentationTestSetup { return EditorViewModel( openPage = openPage, closePage = closePage, + createDocument = createDocument, + createObject = createObject, + createNewDocument = createNewDocument, interceptEvents = interceptEvents, interceptThreadStatus = interceptThreadStatus, updateLinkMarks = updateLinkMark, removeLinkMark = removeLinkMark, - createObject = createObject, reducer = DocumentExternalEventReducer(), urlBuilder = urlBuilder, renderer = DefaultBlockViewRenderer( @@ -349,11 +356,10 @@ open class EditorPresentationTestSetup { toggleStateHolder = ToggleStateHolder.Default(), coverImageHashProvider = coverImageHashProvider ), - createDocument = createDocument, - createNewDocument = createNewDocument, - analytics = analytics, orchestrator = orchestrator, + analytics = analytics, dispatcher = Dispatcher.Default(), + delegator = delegator, detailModificationManager = InternalDetailModificationManager(storage.details), updateDetail = updateDetail, getCompatibleObjectTypes = getCompatibleObjectTypes, @@ -363,13 +369,12 @@ open class EditorPresentationTestSetup { findObjectSetForType = findObjectSetForType, createObjectSet = createObjectSet, copyFileToCache = copyFileToCacheDirectory, - delegator = delegator, + downloadUnsplashImage = downloadUnsplashImage, setDocCoverImage = setDocCoverImage, setDocImageIcon = setDocImageIcon, - downloadUnsplashImage = downloadUnsplashImage, templateDelegate = editorTemplateDelegate, - createNewObject = createNewObject, - simpleTableDelegate = simpleTableDelegate + simpleTableDelegate = simpleTableDelegate, + createNewObject = createNewObject ) }